F#でStateモナドしてみよう。そうですよね、副作用は怖いですものね。
id:NobuhisaさんのWorkflowでモナド - (hatena (diary ’Nobuhisa))に触発されてF#でStateモナドしてみました。
ですが、Haskellのド素人でF#初心者なのでいろいろと間違っているかもしれません。
とりあえず的に、取り急ぎコードをうpしておきます。(F#CTP)
後日、加筆修正するつもりです。
F#でStateモナド
StateMonad.fs
#light namespace Monad ///Stateモナドの器だよ type State<'s, 'a> = State of ('s ->'a * 's);; ///StateBuilderだよ type StateBuilder () = member this.Bind(m, f) = State <| fun s -> let r = match m with | State f -> f s match r with | (v,s) -> match f v with | State f -> f s member this.Return x = State <| fun s -> x, s ///Stateモナドの操作を提供するよ module StateMonad = ///StateBuilderを生成するよ let state = StateBuilder () ///状態を取得するよ。 let Get = fun _ -> State <| fun s -> (s, s) ///状態を更新するよ let Put s = State <| fun _ -> ((), s) ///状態から取得した「値」に関数を適用するよ。(Getの高階関数版) let Gets f = state { let! s = Get () return (f s) } ///関数を適用して状態を更新するよ。(Putの高階関数版) let Modify f = state { let! s = Get () do! Put (f s) } ///Stateモナドを使って行った計算結果(コンテナ)を取得するよ。 let Run (State s) = s ///評価した結果の値を取得するよ。 let Eval (State s) = s >> fst ///実行された状態を取得するよ。 let Exec (State s) = s >> snd
Stateモナドを試してみる
Program.fs
#light open System open Monad.StateMonad module StateTest = type day = | None = 0x0000 | Asa = 0x0001 | Hiru = 0x0002 | Ban = 0x0004 let panyn x = x |> (print_any >> Console.WriteLine) let Test1 = // let s = state // { let! b = Get () // do! Put (b - 5) // let! a = Get () // return (b + 5) |> string // } let s = state { let! b = Gets ((+)5) do! Modify ((+) <| -5) return b |> string } let _ = s |> Run <| 12 |> panyn let _ = s |> Exec <| 12 |> panyn let _ = s |> Eval <| 12 |> panyn Console.WriteLine () () let Test2 = let s = state { let! s = Get () let change (d:day option) = match d with | Some (day.Asa) -> Some <| day.Hiru | Some (day.Hiru) -> Some <| day.Ban | Some (day.Ban) -> Some <| day.Asa | _ -> None do! Put (change s) return match s with | Some (day.Asa) -> Some <| "おはよう" | Some (day.Hiru) -> Some <| "こんにちわ" | Some (day.Ban) -> Some <| "こんばんわ" | _ -> None } let _ = s |> Run <| Some (day.Asa) |> panyn let _ = s |> Run <| Some (day.Hiru) |> panyn let _ = s |> Run <| Some (day.Ban) |> panyn let _ = s |> Run <| Some (day.None) |> panyn Console.WriteLine () let _ = s |> Exec <| Some (day.Asa) |> panyn let _ = s |> Exec <| Some (day.Hiru) |> panyn let _ = s |> Exec <| Some (day.Ban) |> panyn let _ = s |> Exec <| Some (day.None) |> panyn Console.WriteLine () let _ = s |> Eval <| Some (day.Asa) |> panyn let _ = s |> Eval <| Some (day.Hiru) |> panyn let _ = s |> Eval <| Some (day.Ban) |> panyn let _ = s |> Eval <| Some (day.None) |> panyn () #if COMPILED [<STAThread()>] do StateTest.Test1 Console.WriteLine () StateTest.Test2 let key = Console.ReadKey () #endif
実行結果
("17", 7) 7 "17" (Some "おはよう", Some Hiru) (Some "こんにちわ", Some Ban) (Some "こんばんわ", Some Asa) (null, null) Some Hiru Some Ban Some Asa None Some "おはよう" Some "こんにちわ" Some "こんばんわ" None
あ、「///」でコメント付けたら、C#等のXMLコメントみたいに、
インテリセンス表示時にちゃんと説明が表示されるんですね。
今の今まで気付きませんでしたw