これに Haskell のタグを付けたので、その点についてお答えします。Haskell では、CPS 変換を行うのと同等のことがCont
モナドで機能しています。モナドは、値x
を 1 つの引数を取り、それを適用する高階関数に変換します。にx
。
まず、これが通常の Haskell の 1 + 2 です:(1 + 2)
そして、これは継続モナドにあります:
contAdd x y = do x' <- x
y' <- y
return $ x' + y'
...あまり参考になりません。何が起こっているのかを見るために、モナドを逆アセンブルしてみましょう。まず、do
表記を削除します。
contAdd x y = x >>= (\x' -> y >>= (\y' -> return $ x' + y'))
return
関数は値をモナドに持ち上げます。この場合、 として実装されるか\x k -> k x
、中置演算子セクションを として使用し\x -> ($ x)
ます。
contAdd x y = x >>= (\x' -> y >>= (\y' -> ($ x' + y')))
演算子 ( 「(>>=)
バインド」と読む) は、モナド内の計算を連鎖させ、この場合は として実装され\m f k -> m (\x -> f x k)
ます。バインド関数をプレフィックス形式に変更し、ラムダに置き換え、さらにわかりやすくするために名前を変更します。
contAdd x y = (\m1 f1 k1 -> m1 (\a1 -> f1 a1 k1)) x (\x' -> (\m2 f2 k2 -> m2 (\a2 -> f2 a2 k2)) y (\y' -> ($ x' + y')))
一部の関数適用を減らす:
contAdd x y = (\k1 -> x (\a1 -> (\x' -> (\k2 -> y (\a2 -> (\y' -> ($ x' + y')) a2 k2))) a1 k1))
contAdd x y = (\k1 -> x (\a1 -> y (\a2 -> ($ a1 + a2) k1)))
そして、最後の再配置と名前変更のビット:
contAdd x y = \k -> x (\x' -> y (\y' -> k $ x' + y'))
つまり、関数の引数は数値から、期待どおり、数値を取り、式全体の最終結果を返す関数に変更されました。
編集contAdd
:コメント者は、それ自体がカリー化されたスタイルでまだ2つの引数を取ることを指摘しています. 継続を直接使用しないため、これは理にかなっていますが、必須ではありません。それ以外の場合は、最初に関数を引数間で分割する必要があります。
contAdd x = x >>= (\x' -> return (\y -> y >>= (\y' -> return $ x' + y')))
そして、次のように使用します。
foo = do f <- contAdd (return 1)
r <- f (return 2)
return r
これは実際には以前のバージョンと変わらないことに注意してください。最終結果だけでなく、各部分適用の結果を継続としてパッケージ化するだけです。関数は第一級の値であるため、数値を保持する CPS 式と関数を保持する CPS 式の間に大きな違いはありません。
ここでは、何かが継続渡しスタイルである場合のすべてのステップを明示するために、非常に冗長な方法で物事を書いていることに注意してください。
補遺: 最終的な式が、単項式の糖分を取り除いたバージョンと非常によく似ていることに気付くかもしれません。これは偶然ではありません。以前の値に基づいて計算の構造を変更できるようにするモナド式の内側への入れ子の性質は、継続渡しスタイルと密接に関連しているからです。どちらの場合も、ある意味で因果関係の概念を具現化しています。