Life Goes On

まあまあだけど楽しんでる方です

Scala の for-comprehension はモナドなの?

JJUG のカンファレンスで、Scala の for は実はモナドで‥‥みたいなことを聞いたので、具体的にどういうことなのか調べてみました。
for-comprehension (for 内包表記?) は↓みたいな構文です。

scala> for (x <- 0 to 5; y <- 0 to 5 if (x+y)%3==0) yield x*10+y
res1: Seq.Projection[Int] = RangeG(0, 3, 12, 15, 21, 24, 30, 33, 42, 45, 51, 54)

Haskell の List 内包表記と同じ。

Prelude> [ x*10+y | x <- [0..5], y <- [0..5], (x+y)`mod`3==0 ]
[0,3,12,15,21,24,30,33,42,45,51,54]

do 記法だとこんな感じ。こっちの方が似てるかな。

Prelude> do { x <- [0..5]; y <- [0..5]; guard $ (x+y)`mod`3==0; return $ x*10+y }
[0,3,12,15,21,24,30,33,42,45,51,54]

for-comprihension は糖衣構文で、flatMap、map、filter に展開できます。

scala> (0 to 5) flatMap (x => (0 to 5) filter (y => (x+y)%3==0) map (y => 10*x+y))
res2: Seq.Projection[Int] = RangeG(0, 3, 12, 15, 21, 24, 30, 33, 42, 45, 51, 54)

ここで定義に戻ると、モナドとは bind、unit があって以下のモナド則を満たすものです。

  1. bind (unit x) f ≡ f x
  2. bind m unit ≡ m
  3. bind (bind m f) g ≡ bind m (x => bind (f x) g)

この場合 bind(=flatMap)はあるけど unit が無いので、自分で定義する必要があります。
たとえば以下のようにすると、いちおうモナド則も満たしているし、モナド(Listモナド)と言えると思います。
ただ、だから何なの?と言われてしまいそう。
do 記法みたいなものがあれば、その他のモナドにも応用できるので有難みがわくのですが、そうでないのなら敢えてモナドとか言わない方が、混乱しないんじゃないかなと思いました。
[追記]for は do みたいに他のモナドにも使えます。訂正→Scala の for 構文はただのリスト内包表記じゃないよ - Life Goes On[/追記]

scala> def unit (x:Int) = List(x)
unit: (Int)List[Int]

scala> def f (x:Int) = List(1,2,3,4,5) map (y => x+y)
f: (Int)List[Int]

scala> def g (x:Int) = List(1,2) map (y => x*y)
g: (Int)List[Int]

scala> unit (0) flatMap f
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> f (0)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

scala> List(0) flatMap unit
res3: List[Int] = List(0)

scala> List(0)
res4: List[Int] = List(0)

scala> res3 == res4
res5: Boolean = true

scala> List(0) flatMap f flatMap g
res6: List[Int] = List(1, 2, 2, 4, 3, 6, 4, 8, 5, 10)

scala> List(0) flatMap (x => f (x) flatMap g)
res7: List[Int] = List(1, 2, 2, 4, 3, 6, 4, 8, 5, 10)

scala> res6 == res7
res8: Boolean = true

参考サイト: