モナドスタックとliftingの図的理解
スタックは上へ伸びていく
https://gyazo.com/42755688965d51dadec4197727b7ec66
一段のモナド
m aなら
https://gyazo.com/eaa7f7331da7ccfd60b23e4477973295
一般的に言う「モナドスタック」は「モナド」の「スタック」なので、上図のようにaは含めないことが多いと思うmrsekut.icon
知らんけど。
直感的に型として理解しやすいので一応書いている
List Boolなら
https://gyazo.com/90afce973cd3e9ed1735225cceb0daa4
型引数を2つ取る場合のイメージ
State [Int] Int
https://gyazo.com/063bea06ccf886602fc06f8e90014eae
Either String Intなら
https://gyazo.com/cf4cfe0b0a094957ab16b17b9c2045c4
二段のモナド
ReaderT String (State [Int]) Intなら
https://gyazo.com/ca58c9eca84597de7d42a104f1691f3d
lifingのイメージ
途中で謎になったので #WIP にしておくmrsekut.icon*4 liftの必要性が謎になったmrsekut.icon
普通はdo記法の中では、スタックの一番上のモナドとして見られる m aのときは以下のような型になる
code:hs
f = do
a <- ma
undefined
aはa型になる
二段のモナドでもこれは同じ
https://gyazo.com/67719a1202dc82bcb428751e606b4890
code:hs
f :: ReaderT Int (State Int) Bool f = do
r <- ask -- a :: Int
undefined
fがモナド変換子を使っていようがいまいが、Reader Intモナドを使っている感じになる
つまり、上のような書き方をしたときにaの部分にStateが絡んで来ない
ではどうやってStateの部分にアクセスするかというときにliftingを使う liftingのイメージ
最上段にいるわけではないモナドを上段に上げる感じ
https://gyazo.com/29ba61218e514597b19335c6bb331dc6
上段にliftすることで、do記法の中でアクセスできるようになる 下部のモナドのmethodを使えるようになる
code:hs
f :: ReaderT Int (State Int) Bool f = do
r <- ask -- Reader Int なんかのInt
s <- lift get -- State [Int] なんかのInt undefined
getはState用のmethodだが、liftすることでReaderのdoでも使えるようになる
3段になったときのlift
lift . lift
code:hs
ff :: ReaderT Int (StateT Int (Writer String)) Bool ff = do
r <- ask -- Reader Int なんかのInt
s <- lift get -- State [Int] なんかのInt w <- lift.lift.tell $ "hoge" -- Writer String なんかのString
undefined
このコードの場合、liftを省略してもコンパイルエラーにならないなmrsekut.icon
あってもなくてもエラーにならない
なんで?
あれ、liftって使うことあるの?
IOならliftIO使うとしたら、liftっていつ必要になるの?
IO関係ないモナドスタックでliftが必要になるサンプルコードを見たい
liftIOは何をしているか
前提として、IOモナドは常にモナドスタックの最下段にいる
IO用のmethodを呼ぶためにはネストの数だけliftを続ける必要がある
例えば3段モナドの場合
https://gyazo.com/a5adf9fef27e5ee9b3fc9b7b0735262f
もっと多段になったときも同様
https://gyazo.com/05f11eb365c8896676f91b35901276ba
lift.lift.lift ... lift . printのようにしないといけないmrsekut.icon
liftIOによって1発で一番上までliftさせることができる
https://gyazo.com/803d59e2f91668786d5a5352f427cb96
ネストの深さに依存しない
runHogeについて
上から順に適用していく
上から剥いていく感じ
つまり、もっとも上にあるものが一番初めにrunされる
スタックが、Reader>State>Writerの場合 ref code:hs
solve :: ReaderT Int (StateT Int (Writer Int)) Int solve = undefined
main = do
let
r = 100
s = 0
res = runWriter (runStateT (runReaderT solve r) s)
print res
最も最初にrunReaderTを適用しているmrsekut.icon
runReaderTがStateT Int (Writer [Int])) Intを返している
そしてそれにrunStateTを適用する
参考