継続モナド、分かった

参考文献はここ(てかコードの大部分はここからほぼ丸パクリコピペで引用してる)

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に渡す値は同じ意味」って考えておくといいのかも。