matarillo.com

The best days are ahead of us.

C#とScalaのモナド

2018-01-19 07:15:18

『モナドの驚異』にアクセスが多いようなので、便乗してモナドの記事を書いてみる。

Haskellのdo記法に当たる構文糖衣

C#ではLINQのクエリ構文が用意されている。Scalaの場合はfor式だ。

var list1 = Enumerable.Range(0, 5);
var list2 = from x in list1
            select x * 10;
var list3 = from x in list2
            from y in list1
            select x + y;
val list1 = 0 to 5
val list2 = for (x <- list1) yield x * 10
val list3 = for (x <- list2; y <- list1) yield x + y

C#のクエリ構文は、SelectメソッドまたはSelectManyメソッドの呼び出しに機械的に置き換えられる。Scalaのfor式は、mapメソッドやflatMapメソッドの呼び出しに機械的に置き換えられる。それらのメソッドが見つからないときは、クエリ構文やfor式はコンパイルエラーになる。

var list1 = Enumerable.Range(0, 5);
var list2 = list1.Select(x => x * 10);
var list3 = list2.SelectMany(x => list1, (x, y) => x + y);
val list1 = 0 to 5
val list2 = list1.map(x => x * 10)
val list3 = list2.flatMap(x => list1.map(y => x + y))

C#のSelectManymapbind (>>=) が合成されたような形をしているが、ScalaのflatMapbind (>>=) そのものだ。

public Option<TResult> SelectMany<T2, TResult>(
    Func<T, Option<T2>> otherSelector,
    Func<T, T2, TResult> resultSelector);
final def flatMap[B](f: (A)  Option[B]): Option[B]

Haskellのdo記法にはreturnは必須ではない。returnはモナドを返す単なる関数であり、別に使わなくてもdoの最後にモナドがあれば問題ない。 たとえば、printShow a => a -> IO () のようにIOモナドを返すので、こう書ける。

main = do
    args <- getArgs
    print args

C#のクエリ構文はselectが必須だし、Scalaのモナディックなfor式で値を返す時にはyieldが必須なので、Haskellのようには書けない。