昨日もDDDの話題を少ししたので、シナリオ→モデル→コードのサイクルについて身近な例を踏まえてネタを提供できないかと思った。何でもいいんだけど、鍼とか整体とかマッサージとか一度は行った経験あると思うので、そのドメインで考えてみるか。
続きを読むユビキタス言語とドメインモデル、そしてモデル探索のうずまき
最近、ドメイン駆動設計ってどうやって実践すればいいかなーという質問をよくされるので、この記事が満額回答にはならないと思いますが、書いてみたいと思います。
シンプルな問題はトランザクションスクリプト、いわゆる手続き型で対処できます。問題が小さいのでコードは直接的でわかりやすくなる傾向にあります。 とはいえ、世の中の問題はシンプルなものばかりじゃない。複雑な問題もある。DDDの著者であるEric氏は、複雑な問題はドメインモデルを使って対処すべきと説く。
ドメインとは問題の領域とか知識の範囲をいうのですが、DDDはそのドメインにある概念をモデル(ドメインモデル)として定義して、さらに実装として紐付けていく設計手法です。
続きを読むRe: Scala の Trait
trait 便利ですね!
メソッド名が衝突する。 override を指定してやると動く。
scala> (new M with N { override def m = super[N].m }).m()
hi
@rosylilly Scala の Trait
trait Mとtrait Nは継承関係もないので別の型として識別されているから仕方ないんだろうなと推測。まぁ静的型付強い言語でこのような解決手段を提供しているのは、単純にすごいなと思う。とはいえ、もうちょい楽できないかという話はある。というとで、やはり型を意識した方が楽になるね!!! ってことで考えてみた。
次の例では、自分型アノテーションを使って、trait Nはtrait Mにしかミックスインできないようにしてみた。trait Nを定義する時点でメソッドmにoverrideを付与する。 つまりwithで結合した順番で実装が上書きされることになる。
scala> class M { def m = println("hello") } defined class M scala> trait N { this: M => override def m = println("hi") } defined trait N
scala> (new M with N).m hi
次は、trait Lという型を定義し、それを継承したtrait M, Nを用意する。この例では、結合する trait とその順番を変えれば振る舞いを変更することができるようになる。1
scala> trait L { def m: Unit } defined trait L scala> trait M extends L { override def m = println("hello") } defined trait M scala> trait N extends L { override def m = println("hi") } defined trait N scala> (new N with M).m hello scala> (new M with N).m hi
Rubyのmix-inとはだいぶ違う気がするねー。
ドワンゴを退職しました
2/28日付けでドワンゴを退職しました。お世話になった会社なのでちゃんとお礼をいっておこうと思います。
初めてのコンシューマ向けウェブサービスでした。とてもよい経験をさせてもらいました。ありがとうございました。
一番大きいところは、ScalaとDDDを採用したサービスを開発してリリースしたってことですね。そのあたりの詳しい話は Scala Conference 2013 で聞けると思います*1。
Scalaで開発した感想ですが、よく話題になる習得コストはそれほどかからなかったように思います。ある程度先にScalaをやっている人がいれば、その人を軸に最初は命令型(var)で書いて言語仕様も部分的な理解でも問題なかったですね。慣れてきたらvalのコードに挑戦するとよいですね。valでコードが書けるとより関数型スタイルになるので、コードが理解しやすくなります。 こういう風に段階的に学習して、プログラマの成長と共にScalaのコードの表現力も向上していくことができます。だから最初からモナドを理解する必要もないです。逆にScalaだと知らない内にモナドを使っている可能性だってあるわけですから。最初から全部の機能を使いこなせるようにならないと怖いという完璧主義な発想は日本人的だなぁーと最近思ったりします。まぁ、どんどん使って学習の機会を増やした方が効果的だと思います。
もう一方のDDDですが、ドメインをオブジェクト指向で設計するための手法なのですが、自分なりに実践してきたノウハウを実際のサービスの開発に適用してきました。まだまだ道半ばですが、僕が関わったプロダクト以外でも導入しようという機運が高まっているので、次につながる成果が残せたかなと思っています。やっぱり、最重要パターンはユビキタス言語だってことだけは間違いないなーと思っています。まぁ詳しいことは java-ja.DDD で話すので時間のある方はぜひ参加してください。補欠待ちがすごいことになってますが、、、キャンセル待ちも出ると思うし、とりあえず参加登録しておくといいのではないかな。
あと、技術的な実績以外では、友達がたくさんできたことがよかったですね。クズはやっぱり楽しいですね!
本当にお世話になりました。
そして次は本日3/1からグリー株式会社でお世話になります。初日から遅刻しないように頑張るぞ! みなさま、今後ともよろしくお願いします。
追記: あ、大事な欲しいものリストを張っておきますね!
http://www.amazon.co.jp/registry/wishlist/O3X22DRTLI2S
*1:当日は僕も参加しますが登壇はしません。元同僚の後藤さんがしゃべってくれるはず
関数のカリー化と部分適用
今日は、カリー化と関数の部分適用の話題。
Haskellの視座からScalaのカリー化と部分適用を見てみる
まず、Haskellでの関数のカリー化と部分適用についておさらい。
例えば、引数を合計する関数 mysum があるとして、
mysum :: Num a => a -> a -> a -> a mysum a b c = a + b + c
次のようにすると当然期待値を得ることができます。まぁ当然ですね...。
ghci> mysum 1 2 3 6
しかし、このコードをJava脳を用いて読んだ場合、
mysum(1,2,3)
のように読んでしまいがちですが、次のように読むのが正解です。
((mysum 1) 2) 3
Haskellでは関数は必ず一つの引数を取るという考え方があり、2個目の引数は1つ目の引数を取る関数の引数として渡されます。3つ目以降も同様です。これを関数がカリー化されているといいます。
このmysum
関数で考えてみると、mysum
はa -> (a -> a -> a)
です。つまり引数を一つ取り、(a -> a -> a)という関数を戻り値として返す関数と言える。
また、引数が部分的用されたmysum 1
はa -> (a -> a)
です。つまり引数を一つ取り、(a -> a)という関数を戻り値として返す関数。
引数が部分的用されたmysum 1 2
はa -> a
です。つまり引数を一つ取り計算結果を戻り値として返す関数、ということが言えます。
ghci> :t mysum mysum :: Num a => a -> a -> a -> a ghci> :t mysum 1 mysum 1 :: Num a => a -> a -> a ghci> :t mysum 1 2 mysum 1 2 :: Num a => a -> a
この仕組みを利用すると、引数を部分適用した関数を得て、残りの引数を後で適用できるわけです。こんな感じ。
ghci> let mysum_1 = mysum 1 ghci> mysum_1 2 3 6
Scalaでもカリー化と引数の部分適用ができます。
話が逸れますが、Scalaは引数のリストを複数個定義できます。
scala> def sum(a:Int)(b:Int)(c:Int):Int = a + b + c
sum: (a: Int)(b: Int)(c: Int)Int
引数を部分適用せずに普通に呼び出すにはこんな感じ。
scala> sum(1)(2)(3) res0: Int = 6
部分適用した関数が欲しければ、次のようにすればよいです。*1
scala> val sum_1 = sum(1) _ sum_1: Int => (Int => Int) = <function1> scala> sum_1(2)(3) res0: Int = 6
sum_1はカリー化されているのですが、sum自体はカリー化されていません。Int => Int => Int => Int
という型の関数になっていないからですね。ややこしいのですが、sumは複数の引数リストを持っているだけで、カリー化されているわけでないってことですね。
次のようにするとカリー化されたメソッドを定義できます*2。
scala> def sum(a:Int) = (b:Int) => (c:Int) => a + b + c
sum: (a: Int)Int => (Int => Int)
また、次のようなカリー化されていない関数をカリーしたい場合はcurriedメソッド*3が使えます。
scala> def sum(a:Int, b:Int, c:Int):Int = a + b + c scala> val sum_curried = (sum _).curried sum_crried: Int => (Int => (Int => Int)) = <function1>
Haskellの関数定義と同じような形式の関数に変換されました。すべての引数をFunction1型関数の組み合わせで処理する構造に変わりました。 Haskellと同様に引数に部分適用した関数に残りの引数を適用すれば計算結果が得られます。
scala> val sum_1 = sum_curried(1) sum_1: Int => (Int => Int) = <function1> scala> sum_1(2)(3) res0: Int = 6
Dxoでの実例
で、どういうところで使えるわけ?って声が聞こえてきそうなので、Scala(Scalaz)での実例を考えてみました。
ドメインの異なるオブジェクトを相互に変換するためのオブジェクトである、Dxoの実例を紹介します。
DxoはシンプルなFunctorの実装で、Dxoの値(value)にmapの引数に与えた関数fを適用するだけです。適用するとfによって変換されたオブジェクトが返ってきます。 ただし、その関数はf : A => Bでなければなりません。このコード例は、UserAからUserBへの変換を示してします。
import scalaz._ import Scalaz._ case class UserA(name:String) case class UserB(fname:String, lname:String) case class Dxo[A](value:A) object Dxo { implicit object Functor extends Functor[Dxo] { def map[A, B](fa: Dxo[A])(f: A => B): Dxo[B] = { Dxo(f(fa.value)) } } } object Main extends App { val create = {a:UserA => val names = a.name.split(":"); UserB(names(0), names(1))} val append = {(b:UserB, a:UserA) => UserB(b.fname+":"+a.name, b.lname+":"+a.name)}.curried val userB1 = Dxo(UserA("junichi:kato")) map create val userB2 = Dxo(UserA("junichi:kato")) map append(UserB("hoge", "fuga")) println(userB1.value) println(userB2.value) }
create関数は引数にUserAを取って、UserA#nameを":"で分割し、UserBに変換して返す関数です。関数の型はUserA => UserB
です。
val create = {a:UserA => val names = a.name.split(":"); UserB(names(0), names(1))} val userB1 = Dxo(UserA("junichi:kato")) map create
と、ここまでやってることは具体的な変換処理をcreate関数に定義して呼び出しているだけなので単純です。
ここからカリー化と部分適用の出番です。
Dxo#mapはUserAからUserBを得ることなっているのですが、手元にすでにUserBのインスタンスがある場合は(b:UserB, a:UserA) => UserB
の形式の関数を考えたくなります。
この例のappend関数は、適当に既存のUserBとUserAを組み合わせて新しいUserBを生み出す関数ですが、このままじゃmapメソッドに渡すことができません。UserA => UserB
の型でなければなりません。
そこでcurriedでカリー化するわけですね。
カリー化した関数に第一引数であるUserBを部分適用します。そうすると、append(UserB("hoge", "fuga"))
は部分適用された関数なので、残りの引数はUserAのみとなるので関数の型はUserA => UserB
となるわけです*4。map処理の呼び出しは、なんとなくDSLっぽく見えてよい感じですね。
val append = {(b:UserB, a:UserA) => UserB(b.fname+":"+a.name, b.lname+":"+a.name)}.curried val userB2 = Dxo(UserA("junichi:kato")) map append(UserB("hoge", "fuga"))
カリー化した関数の部分適用は、関数の抽象化のためのひとつの手法ということになりますかね。
Scalazを使ってMaybeモナドを自作してみる(後編)
はい。
Scalaz Advent Calendar 2012 12/22 です。
前回に引き続き、MyOptionのためのMonoidとMonadを実装してみます。
Monoidの実装
Monoidはtrait Monoid[F]と定義されているので、今回はMyOptionのInt型の実装を定義します。 コードは次のとおり。
実装すべきメソッドはzeroとappendです。zeroは単位元というらしいです。加法では0が単位元らしい。ググればわかりますw
zeroにはMyNoneを指定します。このappendは二つのMyOptionがMySomeの場合だけ、それぞれの値を加算しMySomeでラップし返します。MyNoneの場合はMyNoneを返します。 実はMonoidはSemigroupを継承していて、appendはSemigroupのメソッドです。zero自体はMonoidで定義されているメソッドです。
object MyOption { implicit object IntMonoid extends Monoid[MyOption[Int]] { def zero = MyNone def append(f1: MyOption[Int], f2: => MyOption[Int]):MyOption[Int] = (f1, f2) match { case (MySome(l), MySome(r)) => MySome(l+r) case _ => MyNone } } }
SemigroupOpsには|+|というメソッドが定義されています。appendを呼び出してくれます。次のコードを実行するとMySome(2)が得られます。
object Main extends App { val op1: MyOption[Int] = MySome(1) val r1 = op1 |+| op1 println(r1) // MySome(2) }
MonoidSyntaxも試してみたいので、isMZeroメソッドを使ってみます。isMZeroのシグニチャを見るとEqual型クラスが必要だとわかるので、そのための実装を次のとおりに定義します。
object MyOption { implicit object IntEqual extends Equal[MyOption[Int]] { def equal(a1: MyOption[Int], a2: MyOption[Int]) = { (a1, a2) match { case (MySome(l), MySome(r)) => l == r case _ => false } } } // IntMonoid ... }
次のコードを実行するとMySomeで値を持っているわけですが、falseになります。
object Main extends App { val op1: MyOption[Int] = MySome(1) val b = op1.isMZero println(b) // false }
Monadの実装
Monadの実装は次の通り。
ソース見ると、MonadはApplicativeとBindを継承していますね。ということで、Applicative#pointと、Bind#bindを実装します。 pointはaをMySomeでラップするだけ。bindの場合はfにMySomeの値を適用、MyNoneの場合MyNoneを返す。やってることはむちゃくちゃ簡単。
object MyOption { implicit object Monad extends Monad[MyOption] { def point[A](a: => A): MyOption[A] = MySome(a) def bind[A, B](fa: MyOption[A])(f: A => MyOption[B]): MyOption[B] = fa match { case MySome(v) => f(v) case MyNone => MyNone } } }
利用例は次のとおり。
BindSyntaxにはflatMapメソッドがあります。
Haskellの>>=
相当のメソッドも使えますね。
object Main extends App { val op1: MyOption[Int] = MySome(1) val f1 = op1.flatMap{x => MySome(x*2)}.flatMap{x => MySome(x+2)} println(f1) // MySome(4) val f2 = op1 >>= {x => MySome(x*2)} >>= {x => MySome(x+2)} println(f2) // MySome(4) }
モナド言えばdo式ですが、Scalaではfor式を使います。 上記とは違う処理内容ですが、MySomeが二重になっている場合の計算も次のようにかけます。
val op2: MyOption[MyOption[Int]] = MySome(MySome(1)) val f3 = for{x <- op2 y <- x} yield y*2 println(f3) // MySome(2)
というわけで、かなり搔い摘んで説明したけど、HaskellでやってたことがScalazでもできますね。 Scalaで、カテゴリに対応する型クラスを実装するなら、Scalazは使えますね。
Scalazを使ってMaybeモナドを自作してみる(前編)
Play or Scala Advent Calendar 2012の 12/21日の記事です。
モナドを理解するために、Haskellでモナドを作ってみたのですが、Scalaz*1でもMaybeモナドを作ってみようと思い試した結果を報告します。
MyOptionを定義する
例のごとくMaybe型を定義します。 Haskellより冗長だけどまぁこんな感じで書けます。
sealed trait MyOption[+T] case object MyNone extends MyOption[Nothing] case class MySome[T](value: T) extends MyOption[T]
Functor型クラスの実装
Scalazでは次のように実装します。implicit objectですね。
object MyOption { implicit object Functor extends Functor[MyOption] { def map[A, B](fa: MyOption[A])(f: A => B): MyOption[B] = { fa match { case MySome(v) => MySome(f(v)) case MyNone => MyNone } } } }
利用する時はこんな感じ。*2
op1にmapメソッドがないけど呼べるのは、FunctorOps
のおかげですね*3。
object Main extends App { val op1: MyOption[Int] = MySome(1) println(op1.map(_ + 1)) }
Applicative型クラスの実装
Functor型クラスの強化版である、Applicative*4型クラスの実装です。
強化版なのでFunctorの機能はそのまま使えます。pointメソッドはaを保持するMySomeを返します。 apメソッドはちょっと難しいですが、MyOptionに通常の値と、MyOptionに関数が格納されていて、その関数に値を適用した結果をMySomeに格納します。
object MyOption { implicit object Applicative extends Applicative[MyOption] { def point[A](a: => A): MyOption[A] = MySome(a) def ap[A, B](fa: => MyOption[A])(f: => MyOption[A => B]): MyOption[B] = { (f, fa) match { case (MySome(l), MySome(r)) => MySome(l(r)) case _ => MyNone } } } }
利用例を見た方が早いですね。op1は値が格納されていて、op2は引数に1を加算して戻り値で返す関数が格納されています。 op3は二つの引数を加算する関数ですね。 r1の例では、<*>を使って関数と値を適用しています。MySomeから値を取り出すことなく。これは便利!
r2の例では値を格納するMySome同士をop3に適用して加算しています。
他にもメソッドあるので試してみるといいですね!
object Main extends App { val op1: MyOption[Int] = MySome(1) val op2: MyOption[Int => Int] = MySome((_:Int) + 1) val op3 = (_:Int) + (_:Int) println(op1.map(_ + 1)) val r1 = op1 <*> op2 println(r1) // MySome(2) val r2 = (op1 <**> op1)(op3) println(r2) // MySome(2) }
Applicativeにはmapメソッドの実装はないのですが、次のようにpointとapで実装できてしまうようです。
override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(fa)(point(f))
ということで、Scalazを使えばHaskellの型クラスの種類を一通り実現できそうです。 後編でMonoidとMonadを実装します*5。