Listモナド
普段使っているリスト[1;2;3]なども実はモナドです。
コンピュテーション式でReturnとBindを適切に設定すればリストモナドとして扱えます。
Listモナドも単純なモナドです。
BindはconcatMap、Returnはリストで包むだけです。定義は簡単でしょう?
Bindの型は'a list -> ('a -> 'b list) -> 'b listになります。
これは次のようにしてみると確かに一致していることがわかります。
Bindの型(concatMap)
> fun l f -> List.concat <| List.map f l;;
val it: l: 'a list -> f: ('a -> 'b list) -> 'b list
それではコードを見ていきます。
Listモナド
let (>>=) (m:'a list) f =
List.concat (List.map f m) // concatMapがないのでconcatとmapで代用
type ListMonadBuilder() =
member __.Return(x) = [x]
member __.Bind(m, f) = m >>= f
member __.Zero() = []
let list = ListMonadBuilder()
//listモナドの利用
let test1 = list {
let! a = [1; 2; 3]
let! b = [10;20;30;40]
return (a + b) }
//シーケンス式で代用
let test2 = [for i in [1;2;3] do for j in [10;20;30;40] do i+j]
//3段にしたリストモナド
let test3 = list {
let! a = [1; 2; 3]
let! b = [10;20;30]
let! c = [100;200;300]
return (a+b+c)
}
let test4 = list {
let! a = [1; 2; 3]
let! b = [10;20;30]
let! c = [100;200;300]
match (a+b+c) with
| x when (a + b/10 + c/100 = 5) -> return (a+b+c)
| _ -> ()
}
printfn "test1 = %A" test1
printfn "test2 = %A" test2
printfn "test3 = %A" test3
printfn "length of test3 = %d" <| List.length test3 // 27
printfn "test4 = %A" test4
test1,test2は同じ結果になっています。
F#にはシーケンス式があるので、リストモナドを使わなくても同じコードがかけます。
(Haskellもリスト内包表記という構文があるので似たことができますが)
test3は3段のネストになった計算をしています。
3要素*3要素*3要素=27要素のリストが結果としてかえってきています。
test4を見る前に、コンピュテーション式としては初出のZero()が出てきています。
これは、コンピュテーション式の値が()になったときに呼び出されます。
test4のmatch式の_のケースで()を返しているのでここで呼び出されています。
もし()をreturn 0などにすると要素の個数は27のままで、abcの各桁の和が5以外になったところは0で埋まります。
()にすることで、不必要な要素は捨てられた結果、abcの各桁の和が5になる6要素だけが帰ってきます。