Readerモナド
Readerモナドは、後に紹介するStateモナドまで一般化されたものが必要ない場合で
共有された環境を利用したいという場面で使えるモナドです。
Readerモナドの環境は読み取り専用です。
Readerモナドは昔は環境モナドと呼ばれていたそうです。
コードでのBindの型は(Reader<'a,'b> -> ('b -> Reader<'a,'c>) -> Reader<'a,'c>)となっています。
Reader<'a,_>をmとみなすといつもの型になっていることがわかります
それではコードを見ていきます。
Readerモナド
// Readerの型。実質関数a -> bをReaderで包むだけ
type Reader<'env,'a> = Reader of action:('env -> 'a)
let run env (Reader action) = action env
let ask = Reader id
let fmap f reader =Reader (fun env -> f (run env reader))
let runReader (Reader x) = x
let local f (Reader m) = Reader (m >> f)
// Bindの実装
let (>>=) m f =
let a env =
let x = run env m
run env (f x)
Reader a
let MyReturn x = Reader (fun _ -> x)
let asks f = ask >>= (MyReturn << f)
type ReaderBuilder() =
member __.Return(x) = MyReturn x
member __.Bind(x,f) = x >>= f
member __.Zero() = Reader (fun _ -> ())
// the builder instance
let reader = ReaderBuilder()
let test1 : Reader<'e,'a> = reader{
let! a = ask //a=10
let! b = asks ((+)1) //b=11
let! c = local ((+)100) ask //c=110
return (a+b+c) //131
}
let test2 = reader{
let! a = Reader ((+)1) //a=11 asksと実質同じもの
let! b = Reader ((*)2) //b=20
let! c = fmap ((+)10) ask //c=20
return (a+b+c) //51
}
let test3 = reader{
let! a = Reader <| Map.tryFind "PATH" // Some "/usr/bin"
// ここでaを使ってなんかする
return a
}
printfn "%A" <| runReader test1 10
printfn "%A" <| runReader test2 10
printfn "%A" <| runReader test3 (Map.ofList [("HOME","/home/hoge");("PATH","/usr/bin")])
test1はあまり意味はありませんがask,asks,localの使い方のサンプルです。
Readerモナドのactionの型はa -> bなので、test2のように1引数関数を直接入れることもできるため
直接関数が扱えるわけではありませんが先に紹介した関数モナドと似たような使い方もできます。
実際はtest3のように環境に相当するものを入れて使うというのが基本的な使い方になると思います。
この記事の作成には
Dependency injection using the Reader monad(英語)
を参考にさせて頂きました。