defunctionalized continuation

Defunctionalization at Work
http://www.brics.dk/RS/01/23/


再帰関数はスタックを使うなどして、一階の関数とループで書ける事はよく知られているけど、その変換を機械的にやる方法。ループにするのは単にCPS変換すればよいけど、それだけだとcontinuationの中に定義したい関数が出てきたりしてよろしくない。なので、continuationをdefunctionalizationという技で一階化してやる


こうして、高階再帰関数を一階のループに落とすと、parser combinatorから有限オートマトンやプッシュダウンオートマトンが得られて、ラムダ計算のcall-by-value evaluatorからCEKマシンが得られる。parser combinatorは普通非決定性を持っていて(非決定性を言語機能として持たない言語では、リストやジェネレータで模倣するけど)一階に落としても非決定性が消えるわけではない。CPS変換やdefunctionalizationに非決定性の有無はあんまり関係ない気がするけど、本当にそうかどうかは知らない。プッシュダウンオートマトンとか割と複雑な定義であるけれども、ちゃんと自然な出自があるということが分かる


#その昔、
http://d.hatena.ne.jp/m-a-o/20070303#p4
同じ技でラムダ計算のevaluatorからabstract machineを得るというのは読んでたけど、当時は理解が全く足りず一般性のある方法だと分かってなかった



例1:CPS階乗関数

fact_cps :: Int -> (Int->Int) -> Int
fact_cps n k | n==0 = k 1
             | otherwise = fact_cps (n-1) (\v -> k (n*v))

fact n = fact_cps n (\x->x)

continuationにはInt->Int型の任意の関数が与えられるけども、実際にはそんな巨大な型は必要ない。continuationを代数的データ型にする

data Cont = C0 | C1 Cont Int

refun :: Cont -> (Int -> Int)
refun C0 = (\v->v)
refun (C1 k n) = (\v -> refun k (n*v))

fact_cps_def :: Int -> Cont -> Int
fact_cps_def n k | n==0 = refun k 1
                 | otherwise = fact_cps_def (n-1) (C1 k n)

fact n = fact_cps_def n C0

よく見ると、Contは[Int]と同じものになる。普通にリストを使うと、以下のような感じになる。長ったらしいけど、要するにfoldl (*) 1 [1..n]

fact_loop n ls | n==0 = aux ls 1
               | otherwise = fact_loop (n-1) (n:ls)
  where
    aux [] n = n
    aux (x:xs) n = aux xs (n*x)

fact n = fact_loop n []

例2:CPS fibonacci関数

fib_cps n k | n==0 = k 1
            | n==1 = k 1
            | otherwise = fib_cps (n-1) (\v1 -> fib_cps (n-2) (\v2 -> k (v1+v2)))

fib n = fib_cps n (\x->x)
data Cont = C0 | C1 Cont Int | C2 Cont Int

refun C0 = (\v -> v)
refun (C1 k v1) = (\v2 -> refun k (v1+v2))
refun (C2 k n) = (\v1 -> fib_cps_def (n-2) (C1 k v1))

fib_cps_def n k | n==0 = refun k 1
                | n==1 = refun k 1
                | otherwise = fib_cps_def (n-1) (C2 k n)

fib n = fib_cps_def n C0

普通にリストを使って書くと、あまり見慣れない形のものになる

fib_loop n ls | n==0 = aux ls 1
              | n==1 = aux ls 1
              | otherwise = fib_loop (n-1) ((Right n):ls)
  where
    aux [] n = n
    aux (Left n:xs) v1 = aux xs (n+v1)
    aux (Right n:xs) v1 = fib_loop (n-2) ((Left v1):xs)

fib n = fib_loop n []


例3:クイックソート

qsort [] = []
qsort (x:xs) = qsort [c|c<-xs , c<x] ++ [x] ++ qsort [c|c<-xs , c>=x]

CPS変換

qsort_cps [] k = k []
qsort_cps (x:xs) k = qsort_cps [c|c<-xs , c<x] (\lt -> qsort_cps [c|c<-xs , c>=x] (\gt-> k $ lt++[x]++gt))
qsort xs = qsort_cps xs (\x->x)

defunctionalization of continuation

data Cont a = C0 | C1 (Cont a) [a] a | C2 (Cont a) [a] a
refun C0 = \x->x
refun (C1 k xs x) = \lt -> qsort_cps_def [c|c<-xs , c>=x] (C2 k lt x)
refun (C2 k lt x) = \gt -> refun k $ lt++[x]++gt

qsort_cps_def [] k = refun k []
qsort_cps_def (x:xs) k = qsort_cps_def [c|c<-xs , c<x] (C1 k xs x)
qsort xs = qsort_cps_def xs C0

スタックには、二種類のラベルされたペア(a,[a])を積んでいく

qsort_loop (x:xs) stk = qsort_loop [c|c<-xs , c<x] $ (Left (x,xs)):stk
qsort_loop [] stk = aux stk []
  where
    aux [] rest = rest
    aux ((Left (x,xs)):stk) rest = qsort_loop [c|c<-xs,c>=x] $ (Right (x,rest)):stk
    aux ((Right (x,xs)):stk) rest = aux stk $ xs++[x]++rest

qsort xs = qsort_loop xs []


例4:プッシュダウンオートマトンの導出
parser combinatorは、普通やるように、matchした残りの文字列を返す関数として定式化して、あとは同じようにやっていく。具体例は書くのがめんどくさくなったので、略(論文に書いてある