CPS refresher

Aus ProgrammingWiki

Wiki-Quelltext dieser Seite.

<language>Scheme</language>

==CPS Refresher==

To refresh our memory, let's CPS the factorial procedure. Here is the definition of <tt>fact</tt> in direct style.

<code id="4b41f6608c2ec">
(define fact
  (lambda (n)
    (cond
      [(zero? n) 1]
      [else (* n (fact (sub1 n)))])))
</code>
<run id="4b41f6609018c">
(fact 5)
</run>

Here is <tt>fact-cps</tt>, which is the CPSed version of <tt>fact</tt>.

<code id="4b41f6608cab1">
(define fact-cps
  (lambda (n k)
    (cond
      [(zero? n) (k 1)]
      [else (fact-cps (sub1 n) (lambda (v)
                                 (k (* n v))))])))
</code>

We could use the identity procedure <tt>(lambda (v) v)</tt> as the initial continuation,

<run id="4b41f66090954">
(fact-cps 5 (lambda (v) v))
</run>

but we want to signal an error if the initial continuation is invoked more than once. Therefore, we will use the continuation returned from calling the following <tt>empty-k</tt> procedure:

<code id="4b41f6608da57">
(define empty-k 
  (lambda ()
    (let ([okay #t])
      (lambda (v)
        (if okay
            (begin
              (set! okay #f)
              v)
            (error 'empty-k "k invoked in non-tail position"))))))
</code>

<code id="4b420bc6345ff">
(define cont (empty-k))
</code>

<run id="4b41f66091123">
(cont 120)
</run>

<run id="4b41f66091123">
(cont 120)
</run>

<run id="4b41f66090954">
(fact-cps 5 (empty-k))
</run>

Since <tt>fact-cps</tt> consumes two arguments instead of one, we might find it convenient to also define a "driver" procedure called <tt>fact</tt>.

<code id="4b41f6608d290">
(define fact
  (lambda (n)
    (fact-cps n (empty-k))))
</code>

<run id="4b41f7e7c5d78">
(fact 5)
</run>

Just for fun, and to demonstrate how <tt>empty-k</tt> behaves, let's write an ''incorrect'' definition of <tt>fact-cps</tt> that invokes the continuation <tt>k</tt> ''multiple'' times.

<code id="4b41f6608e223">
(define fact-cps
  (lambda (n k)
    (cond
      [(zero? n) (k 1)]
      [else (fact-cps (sub1 n) (lambda (v) (k (* n (k v)))))])))
</code>

When we test our new version, we get an error:

<run id="4b41f660918f7">
(fact-cps 5 (empty-k))
</run>

Here is one more example, using the Fibonacci procedure. First we define <tt>fib</tt> in direct style.

<code id="4b41f6608e9f3">
(define fib
  (lambda (n)
    (cond
      [(zero? n) 1]
      [(= n 1) 1]
      [else (+ (fib (sub1 n)) (fib (sub1 (sub1 n))))])))
</code>

<run id="4b41f961b77e3">
(fib 8)
</run>

Now we transform <tt>fib</tt> into continuation-passing style.

<code id="4b41f6608f1ea">
(define fib-cps
  (lambda (n k)
    (cond
      [(zero? n) (k 1)]
      [(= n 1) (k 1)]
      [else (fib-cps (sub1 n) (lambda (v)
                                (fib-cps (sub1 (sub1 n)) (lambda (w)
                                                           (k (+ v w))))))])))
</code>

Once again, we can define a driver procedure if we like.

<code id="4b41f6608f9b0">
(define fib
  (lambda (n)
    (fib-cps n (empty-k))))
</code>

<run id="4b41f961b7fa8">
(fib 8)
</run>
 

Found on http://www.cs.indiana.edu/cgi-pub/lkuper/c311/cps-refresher, slightly modified by CW. <br/>
cps-refresher.txt · Last modified: 2009/10/08 09:10 by lkuper