CPS变换

2016-07-28
5分钟阅读时长

Continuation

Continuation 表示一个表达式被求值之后,接下来要做的事情

call/cc(call一个continuation)

call/cc必须passed一个有 一个参数 的procedure

(call/cc
 (lambda (k)
   (* 5 4))) ;; => 20

获得一个k的continuation,然而并没有被使用

(call/cc
 (lambda (k)
   (* 5 (k 4)))) ;; => 4

获得一个k的continuation,(k 4)时直接到(call/cc …)外

set!的continuation

continuation被外部变量set!,调用时回到(call/cc …)外

(define k-cc #f)
(+ (call/cc (lambda (k)
	      (begin
		(set! k-cc k)
		(k (* 3 4))))) ;; k-cc run from here
   5) ;; => 17
(k-cc 5) ;; => 10

(set! k-cc k)后*k*就是(call/cc …)外的continuation

(k-cc 5) => (+ 5 5) => 10

非引用透明性(call/cc (lambda (x) x))

自己被用来做了什么事,它返回的continuation就对别人做同样的事

(let ([x (call/cc (lambda (k) k))])
  (x (lambda (ignore) "hi"))) ; => "hi"

=>

(let ([x (lambda (ignore) "hi")])
  (x (lambda (ignore) "hi")))

=>

((lambda (ignore) "hi") (lambda (ignore) "hi"))

=>

"hi"

(define (get-cc) (call/cc (lambda (k) k)))
(set! x (get-cc)) ;; x 被set!
(x 10) ;; x 被set! (set!之后会有Error)
;; x => 10

货真价实的10 已经不带任何continuations绑定

CPS

CPS 能写出更有格(nan)调(dong)的递归程序

自然递归

(define (fact n)
  (cond [(zero? n) 1]
	[else (* (fact (sub1 n)) n)]))

APS(Accumulator-Passing Style)

使用一个值维护递归

(define (fact-aps n acc)
  ;; accumulator-passing style
    (cond [(zero? n) acc]
	  [else (fact-aps (sub1 n) (* acc n))]))

(define (fact2 n) (fact-aps n 1))

REPL

(trace fact-aps) ;; (require racket/trace)
(trace fact2)
(fact2 5)

输出

>(fact2 5)
>(fact-aps 5 1)
>(fact-aps 4 5)
>(fact-aps 3 20)
>(fact-aps 2 60)
>(fact-aps 1 120)
>(fact-aps 0 120)
<120
120

CPS(Continuation-Passing Style)

使用一个过程维护递归(需要更多的内存)

(define (fact-cps n k)
  ;; continuation-passing style
    (cond [(zero? n) (k 1)]
	  [else
	   (fact-cps (sub1 n)
		     (lambda (v)
		       (k (* v n))))]))

(define (fact3 n)
  (fact-cps n (lambda (v) v)))

REPL

(trace fact-cps) ;; (require racket/trace)
(trace fact3)
(fact3 5)

输出

>(fact3 5)
>(fact-cps 5 #<procedure>)
>(fact-cps 4 #<procedure>)
>(fact-cps 3 #<procedure>)
>(fact-cps 2 #<procedure>)
>(fact-cps 1 #<procedure>)
>(fact-cps 0 #<procedure>)
<120
120

CPS变换

CPS变换用于desugar(zhuanbi)

符号约定

文字约定

number := 数字
boolean := 布尔值
symbol := 符号(变量名),不包括k

k,arg, value

lambda, if, quote

sexp为要处理的输入

sexp := self-evaluate
      | symbol
      | (quote anything)    ; 引用形式
      | (lambda (arg) sexp) ; lambda表达式
      | (sexp sexp)         ; 调用
      | (if test then else)

self-evaluate := number | boolean

我们把CPS函数的输出叫做cps-exp,sexp对应的cps-exp它应该是这样的:

cps-exp := simple-exp
         | (k simple-exp)
         | (simple-exp simple-exp k-exp)
         | (if simple-exp cps-exp cps-exp)

k-exp := (lambda (value) cps-exp)
simple-exp := self-evaluate | symbol | (quote anything)
            | (lambda (arg k) cps-exp)

这样,CPS变换就是实现sexp到cps-exp的变换

self-evaluate, symbol, quote的处理

(define cps
  (lambda (sexp)
    ...))

考虑sexp的前几种情况

sexp := self-evaluate
      | symbol
      | (quote anything)    ; 引用形式

而应该输出的cps-exp是这样的

cps-exp := simple-exp
simple-exp := self-evaluate | symbol | (quote anything)

观察以下

(cps 1)
;; =>
1

这里我们不用做任何事

(define cps
  (lambda (sexp)
    (match sexp
      [(? self-evaluate?) sexp]
      [(? symbol?) sexp]
      [`(quote ,thing) sexp])))

(sexp sexp)

考虑过程的调用

sexp期待的输入应该是这样的

sexp := (sexp sexp)

而应该输出的cps-exp是这样的

cps-exp := (simple-exp simple-exp k-exp)
k-exp := (lambda (var) cps-exp)
simple-exp := self-evaluate | symbol | (quote anything)

把前一个sexp称作rator,后一个sexp称作rand

则输入看起来应该是这样子的

`(,rator ,rand)

这里的rator和rand又是sexp, 因此需要调用cps函数

(lambda (simple-rator simple-rand)
  _cps-exp_)

考虑到 cps-exp := (simple-exp simple-exp k-exp)

(lambda (simple-rator simple-rand)
  (list simple-rator simple-rand _k-exp_))

而这个 k-exp := (lambda (value) cps-exp)

(lambda (simple-rator simple-rand)
  (list simple-rator simple-rand
        `(lambda (var) _cps-exp_)))

这里我们又遇到了 cps-exp,但我们已经完成了一个变换了

我们需要一个参数来帮助我们回到cps的开始

我们可以把这个参数放在(cps)上

(define cps
  (lambda (sexp ctx) ...)

现在我们可以让ctx来帮助我们做重复的事了

(cps rator
     (lambda (simple-rator)
       (cps rand
            (lambda (simple-rand)
              (list simple-rator simple-rand
                    `(lambda (var) ,(ctx 'var)))))))

加入ctx后我们还需要修改之前的情况

(define cps
  (lambda (sexp ctx)
    (match sexp
      [(? self-evaluate?) (ctx sexp)]
      [(? symbol?) (ctx sexp)]
      [`(quote ,thing) (ctx sexp)]
      [`(,rator ,rand)
       (cps rator
            (lambda (simple-rand)
              (list simple-rator simple-rand
                    `(lambda (var) ,(ctx 'var)))))])))

显然这里的ctx都应该是identify

(define identify (lambda (v) v))

Lambda表达式

期待的sexp输入

sexp-exp := (lambda (arg) exp)

对应的cps-exp

cps-exp := simple-exp
         | (k simple-exp)
         | (simple-exp simple-exp k-exp)

simple-exp := (lambda (arg k) cps-exp)

输入看起来应该是这样子的

`(lambda (,arg) ,sexp)

(lambda (arg k) …)显然是一个simple-exp,可以让ctx来完成

(ctx `(lambda (,a k) _sexp_))

这个 sexp 交给cps递归

[`(lambda (,arg) ,sexp)
 (ctx `(lambda (,arg k) ,(cps sexp _?_)))]

关于 ? 的内容可以先看几个例子

((lambda (a) a) 1)
;; =>
;; ((lambda (a k) (k a)) 1 _?_)
(lambda (a) (f (g a)))
;; =>
;; (lambda (a k) (g a (lambda (v) (f v (lambda (v) (k v))))))

看的出来,所有表达式最后都会通过(k x)来返回

我们可以把这个ctx记下来,叫做ctx0

(define ctx0
  (lambda (v)
    `(k ,v)))

也就是我们需要的的 ?

[`(lambda (,arg) ,sexp)
 (ctx `(lambda (,arg k) ,(cps sexp ctx0)))]

if表达式

经过前面的推导,现在这个变换已经很明显了

[`(if ,test ,then ,else)
 (cps test (lambda (simple-test)
             `(if ,simple-test
                  ,(cps then ctx)
                  ,(cps else ctx))))]

cps变换

最后,得到了我们的cps变换

(define self-evaluate?
  (lambda (v)
    (or (number? v) (boolean? v))))

(define identify (lambda (v) v))

(define ctx0 (lambda (v) `(k ,v)))

(define cps
  (lambda (sexp ctx)
    (match sexp
      [(? self-evaluate?) (ctx sexp)]
      [(? symbol?) (ctx sexp)]
      [`(quote ,thing) (ctx sexp)]
      [`(if ,test ,then ,else)
       (cps test (lambda (simple-test)
                   `(if ,simple-test
                        ,(cps then ctx)
                        ,(cps else ctx))))]
      [`(lambda (,arg) ,sexp)
       (ctx `(lambda (,arg k) ,(cps sexp ctx0)))]
      [`(,rator ,rand)
       (cps rator
            (lambda (simple-rator)
              (cps rand
                   (lambda (simple-rand)
                     (list simple-rator simple-rand
                           `(lambda (var) ,(ctx 'var)))))))])))

改进

一个简单的cps变换已经完成了,但是我们还可以对其进行改进以便于生成更加简洁的结果

improve-1

(cps '(lambda (v) (f a)) identify)
;; =>
;; '(lambda (v k) (f a (lambda (var) (k var))))

这里的(lambda (var) (k var))可以做一个beta-规约直接变成k 所以

[`(,rator ,rand)
       (cps rator
            (lambda (simple-rator)
              (cps rand
                   (lambda (simple-rand)
                     (if (eq? ctx ctx0)
                         (list simple-rator simple-rand 'k)
                         (list simple-rator simple-rand
                               `(lambda (var) ,(ctx 'var))))))))]
(cps '(lambda (v) (f a)) identify)
;; =>
;; '(lambda (v k) (f a k))

improve-2

有些只接受单一参数而不能接受k参数的过程(原语)

我们需要在`(,ractor ,rand)里处理

simple-exp := (primitive-rator simple-exp)

枚举这些原语过程

(define primitive-rator?
  (lambda (x)
    (memq x '(add1 sub1 zero? car cdr))))

修改cps过程

[`(,rator ,rand)
 (cps rator
      (lambda (simple-rator)
        (cps rand
             (lambda (simple-rand)
               (cond [(primitive-rator? simple-rator)
                      (ctx `(,simple-rator ,simple-rand))]
                     [(eq? ctx ctx0)
                      (list simple-rator simple-rand 'k)]
                     [else
                      (list simple-rator simple-rand
                            `(lambda (var) ,(ctx 'var)))])))))]

如果加入接受多个参数的过程作为原语

[`(,op ,a ,b)
       (cps a (lambda (a)
                (cps b (lambda (b)
                         (ctx `(,op ,a ,b))))))]

会引起命名冲突

(cps '(+ (f a) (g b)) identify)
;; =>
;; => '(f a (lambda (var) (g b (lambda (var) (+ var var)))))

修改`(,rator ,rand)的情况可以解决

(list simple-rator simple-rand
      (let ([v (gensym "v")])
        `(lambda (,v)
           ,(ctx v))))

此时if的(gensym "v")显得有些浪费,可以改成

[`(if ,test ,then ,else)
 (cps test (lambda (simple-test)
             (if (memq ctx `(,ctx0 ,identify))
                 `(if ,simple-test
                      ,(cps then ctx)
                      ,(cps else ctx))
                 `(let ([k (lambda (v) , (ctx 'v))])
                    (if ,simple-test
                        ,(cps then ctx0)
                        ,(cps else ctx0))))))]

现在运行

(cps '(f (if a b c)) identify)
;; =>
;; '(let ((k
;;         (lambda (v)
;;           (f v (lambda (v14263) v14263)))))
;;    (if a (k b) (k c)))

可以得到更为美观的效果

最终代码

(define self-evaluate?
  (lambda (v)
    (or (number? v) (boolean? v))))

(define ctx0 (lambda (v) `(k ,v)))

(define identify (lambda (x) x))

(define cps
  (lambda (sexp ctx)
    (match sexp
      [(? self-evaluate?) (ctx sexp)]
      [(? symbol?) (ctx sexp)]
      [`(quote ,thing) (ctx sexp)]
      [`(if ,test ,then ,else)
       (cps test (lambda (simple-test)
                   (if (memq ctx `(,ctx0 ,identify))
                       `(if ,simple-test
                            ,(cps then ctx)
                            ,(cps else ctx))
                       `(let ([k (lambda (v) , (ctx 'v))])
                          (if ,simple-test
                              ,(cps then ctx0)
                              ,(cps else ctx0))))))]
      [`(lambda (,arg) ,sexp)
       (ctx `(lambda (,arg k) ,(cps sexp ctx0)))]
      [`(,rator ,rand)
       (cps rator
            (lambda (simple-rator)
              (cps rand
                   (lambda (simple-rand)
                     (if (eq? ctx ctx0)
                         (list simple-rator simple-rand 'k)
                         (list simple-rator simple-rand
                               (let ([v (gensym "v")])
                                 `(lambda (,v)
                                    ,(ctx v)))))))))]
      [`(,op ,a ,b)
       (cps a (lambda (a)
                (cps b (lambda (b)
                         (ctx `(,op ,a ,b))))))])))
上一页 Haskell指南
下一页 Lambda表达式