継続モナド、分かった
参考文献はここ(てかコードの大部分はここからほぼ丸パクリコピペで引用してる)
https://qiita.com/sparklingbaby/items/2eacabb4be93b9b64755
まず「継続渡しスタイル」ってのがあって、こいつは関数を積み重ねていく。
関数側の視点で見れば、「自分のやることを済ました後に、引数として渡された関数に自分が処理した値を渡す」ということをする。
なので継続モナドを使った以下のコード
cpsVal :: (Int -> r) -> r
cpsVal f = f 30
cpsSum :: Int -> Int -> (Int -> r) -> r
cpsSum a b f = f (a + b)
contcalc :: Int
contcalc = flip runCont id $ do
a <- cont cpsVal
b <- cont $ cpsSum a 1
return $ b + 2
と継続モナドを使わない以下のコードはほぼ同じである。
calc :: Int
calc =
let
a = 30
b = a + 1
in
b + 2
まあ、少し違いはある。継続モナドの方はまだ続きの関数が来るのを待っている(つまり「引数として関数をとる関数」となっている)。最後に継続モナドを終了させたいときには、恒等関数id
が使える。id
がここにきて("終了"という意味で)特別扱いされる。
calc
に比べcontcalc
に余分に付いているflip runCont id
がそれ。
まずここまでが継続モナドの基本。
ここから、この継続モナドを使って早期return的なことができるようになる。
それがcallCC
。
callCCの中身は使う側としてはどうでもいいので置いておいて、使う際のコードを示すと:
ten = flip runCont id $ do
n <- callCC $ \exit -> do
exit 2
return 3
return $ n * 5
で、ten
は10と評価される。
「callCC
に渡すラムダ式の引数のexit
って何者…?」ってなるんだけど、これはcallCC
側で提供してくれる早期return用関数と思っておけばいい
ちなみに以下2つはどちらも2と評価される。
two = flip runCont id $ do
n <- callCC $ \exit -> do
cont (const 2)
exit 3
return (n * 5)
two' = flip runCont id $ do
n <- callCC $ \exit -> do
cont (const 2)
return 3
return $ n * 5
const 2
が全部無視している。
さらにgetCC
というシロモンを定義すると、do-whileが構成できる。(mtlにはlabel_として存在している)
getCC :: ContT r m (ContT r m a)
getCC = callCC $ \exit ->
let a = exit a
in return a
echo = flip runContT pure $ do
goto <- getCC
line <- lift getLine
lift $ putStrLn line
when (line /= "bye")
goto
派生のgetCC'
を定義すると、変化する値の情報付きでdo-whileができる。(mtlではlabel)
getCC' :: a -> ContT r m (a, a -> ContT r m b)
getCC' a = callCC $ \exit ->
let
f b = exit (b, f)
in
return (a, f)
print10 = flip runContT pure $ do
(x, goto) <- getCC' 0
lift $ print x
when (x < 10) $
goto $ x + 1
「getCC'
とgoto
に渡す値は同じ意味」って考えておくといいのかも。