ようこそ。睡眠不足なプログラマのチラ裏です。

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