GhaSShee


CPS


# Continuation Passing Style CPS is a continuation monad.
CPS is a copresheaf functor on category C e.g. like Hask.
CPS transformation is just a (op)Yoneda embedding.
## CPS in Category Theory Style The CPS Transformation on Type `K` is a Copresheaf Functor over the category `Hask`. ~~~ haskell CPS(K) :: Hask → Hom(Hask,Set) CPS(K) := Hom(Hom(-,K),K) CPS(K) f = - . f ~~~ CPS transformation is just a (op)Yoneda.
Its preudo-Haskell-like syntax is written as follows; ~~~ haskell CPS :: Hask → (B→A) → (A→K) → (B→K) CPS [K] f k = k . f ~~~ Taking quantification on `K`, and substituting `B = 1`, we will get; ~~~ haskell CPS A :: ∀K . (A→K)→K ~~~ Now we define the Syntax of categorical version of `STL (Simply Typed Lambda Calculus)` as follows; ~~~ haskell t ::= x | λx.M | (M . N) ~~~ Assuming a new Notation `(-)* := CPS`, we get; ~~~ haskell (x)* k := k . x (λx.M)* k := k . (λx.M) = M* . (λm.k(λx.m)) (M . N)* k := k . (M . N) = M* . (λm.k . (m . N)) = M* . (λm.N* . (λn.k(m . n))) ~~~ Here, nonCPS term `t` is got by `t == t* id id` .
The first argument `id` is the continuation of `t` which is the most outer continuation (or the last continuation).
The second argumennt `id` is the element of Unit Type `1` with which one can get elements in Category Theory.
## [Japanese] CPS in Category Theory Style `K` 上の CPS 変換 とは、圏 `Hask` 上の Copresheaf Functor です。 ~~~haskell CPS(K) :: Hask -> Hom(Hask,Set) CPS(K) = Hom(Hom(-,K),K) CPS(K) f = - . f ~~~ これは単なる (op)Yoneda Embedding です。
CPS 変換とはただの (op)Yoneda です。 Haskell 風に書くと ~~~ haskell CPS :: Hask -> (B->A) -> (A->K) -> (B->K) CPS (K) f = \k -> k f ~~~ となります。
`K` に関して量化し、`B = 1` とすると、 ~~~ haskell CPS A :: ∀K . (A→K)→K ~~~ を得ます。 これをそのまま 圏論ライクな単純型付λ計算 `t ::= x | λx.M | (M . N) ` 上で実装しますと、
`(-)* := CPS` として、 ~~~haskell (x)* k := k . x (λx.M)* k := k . (λx.M) = M* . (λm.k(λx.m)) (M . N)* k := k . (M . N) = M* . (λm.k . (m . N)) = M* . (λm.N* . (λn.k(m . n))) ~~~ となります。
`t == t* id id` として得られます。
(ここで、一つ目の `id` は `t` の継続、二つ目の `id` は型 `1` (Unit型)の要素です。) ## [Japanese] CallByValue CPS 教科書で一般的な CPS 変換は少し様子が異なるように思うかもしれませんが、本質的に同じものです。
これは、上記の二つ目の `id` を省略し、 `t == t* id` で得られるように折りたたんだために、
根源的な余前層の構造が潰れてしまっているように見えているともいえます。
あるいは、実装の観点に立った `Object First` の見方ともいうことができます。
Reynold による `CBV version` の CPS は、そのため、 ~~~haskell (x)* k := k x (λx.M)* k := k (λx.M*) (M N)* k := (M N) k = M* (λm.(m N) k) = M* (λm.N* (λn.(m n) k)) ~~~ となります。 ## [Japanese] Lazy CPS Lazy な評価戦略では、λ式の中身は放っておいても良いため、以下のようになります。
本質的に上の Relynold による CPS と同じ考え方を当てはめるです。
Plotkin による `Lazy version` の CPS は、 ~~~haskell (x)* k := x k (λx.M)* k := k (λx.M*) (M N)* k := (M N) k = M* (λm.(m N) k) ~~~ となります。こちらも、`t == t* id` です。 # [Japanese] Haskell での実装 https://wiki.haskell.org/MonadCont_done_right  Monad として、Continuation を処理するのが一般的です。
CPS 変換することで、 1. 例外処理や、 2. 探索の break 3. SAT solver を全探索せずに終了させるなど、 「あらかじめ想定されていた全域処理を途中で止める」という通常の再帰的な関数ではできないことを可能にします。
簡単な誤植などありますが、このことについて非常に良く述べられた記事があります。
https://free.cofree.io/2020/01/02/cps/ 以下の例は、実際に動作する Haskell のコードです。 ~~~haskell {-# LANGUAGE UnicodeSyntax #-} {-# LANGUAGE BangPatterns #-} module Cont.CPS where type CPS a' = ∀ k . (a' -> k) -> k toCPS :: a -> CPS a toCPS = flip ($) fromCPS :: CPS a -> a fromCPS = ($ id) -- fromCPS k = k id callCC :: ((a->(b->k)->k)->(a->k)->k) -> (a->k) -> k callCC f h = f (\a _->h a) h ~~~ ## [Japanese] Factorial Function in CPS ~~~haskell fac :: Integer -> Integer fac 0 = 1 fac n = n * fac (n-1) -- >>> fac n -- |-> n * (n-1) * ... * 2 * 1 * 1 facCPS :: Integer -> (Integer -> k) -> k facCPS 0 k = k 1 facCPS n k = facCPS (n-1) $ k . (*n) -- >>> facCPS 10 id -- |-> ((...(( id . (*n) ) . (*(n-1) )) . ... ) . (*2) ) 1 -- |-> (*n) . ... . (*2) . (*1) $ 1 facIterative :: Integer -> Integer facIterative = go 1 where go !acc 0 = acc go !acc n = go (acc*n) (n-1) -- Much Faster ~~~ ## [Japanese] Tree Search Break in CPS 二分木に6 が出れば直ちに探索を終了し、それ以外は、総和を出力する ~~~haskell data BTree = Br BTree BTree | Lf Int lfSum (Lf x) = x lfSum (Br l r) = lfSum l + lfSum r lfSumCPS (Lf x) k = k x lfSumCPS (Br l r) k = lfSumCPS l ( \l' -> lfSumCPS r (\r' -> k (l'+r'))) -- if there is a lf which has value 6, return 1000 -- else return sum lfSumCPS' (Lf 6) k = 1000 lfSumCPS' (Lf x) k = k x lfSumCPS' (Br l r)k = lfSumCPS' l ( \l' -> lfSumCPS' r (\r' -> k (l'+r'))) -- ## CONCLUSION ## -- With CPS, -- we can quit the tree search at any points which we want . ~~~ ## [Japanese] Right Associativity in CPS ### Monoid ~~~haskell -- ################################# -- ## Associativity of Monoid ## -- ################################# chainCPS :: ((a->k)->k) -> (a->((b->k)->k)) -> (b->k) -> k chainCPS s f = s . flip f (<->) = chainCPS monoidToCPS :: Monoid a => a -> (()->a) -> a monoidToCPS a = (a <>) . ($ ()) monoidFromCPS :: Monoid a => ((() -> a) -> a) -> a monoidFromCPS cps = cps (const mempty) -- e.g. sumL = ([1,2,3] <> [4,5,6]) <> [7,8,9] sumR = monoidFromCPS $ monoidToCPS [1,2,3] <-> (\_ -> monoidToCPS [4,5,6] <-> (\_ -> monoidToCPS [7,8,9])) sumR' = [1,2,3] <> ([4,5,6] <> [7,8,9]) ~~~ ### Monad ~~~haskell -- ################################## -- ## Monad / CPS ## -- ################################## monadToCPS :: Monad m => m a -> (forall k. (a -> m k) -> m k) monadToCPS ma = (ma >>=) monadFromCPS :: Monad m => (forall k. (a->m k)->m k) -> m a monadFromCPS cps = cps pure -- e.g. resL = [1,2,3] >>= (\x -> [x+1]) >>= (\y -> [y-2]) resL' = [1,2,3] >>= (\x -> [x+1]) >>= (\y -> [y-2]) resR = [1,2,3] >>= (\x -> [x+1] >>= (\y -> [y-2])) resR' = monadFromCPS $ monadToCPS [1,2,3] <-> (\x -> monadToCPS [x+1]) <-> (\y -> monadToCPS [y-2]) ~~~ ## [Japanese] SAT solver in CPS ~~~haskell module Cont.Sat where import Data.Map data BF = Var String | Not BF | And BF BF | Or BF BF -- SAT solver can be implemented with CPS -- fail is continuation sat :: BF -> Map String Bool -> (Bool -> Map String Bool -> k -> k) -> k -> k sat bf asst succ fail = case bf of Var v -> case asst !? v of Just b -> succ b asst fail Nothing -> let asstT = insert v True asst asstF = insert v False asst tryT = succ True asstT tryF tryF = succ False asstT fail in tryT Not bf' -> sat bf' asst (succ . not) fail And l r -> sat l asst succAnd fail where succAnd True asstAnd failAnd = sat r asstAnd succ failAnd succAnd False asstAnd failAnd = succ False asstAnd failAnd Or l r -> sat l asst succOr fail where succOr True asstOr failOr = succ True asstOr failOr succOr False asstOr failOr = sat r asstOr succ failOr solve :: BF -> Maybe (Map String Bool) solve bf = sat bf empty (\b asst fail -> if b then Just asst else fail) Nothing ~~~ # References * https://www.wikiwand.com/ja/継続渡しスタイル * https://free.cofree.io/2020/01/02/cps/ * https://wiki.haskell.org/MonadCont_done_right * https://www.haskellforall.com/2012/12/the-continuation-monad.html # Continuation-and-environment-passing style https://hal.inria.fr/hal-01972846/document