Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Continuation Passing Style In Common Lisp?

In an effort to find a simple example of CPS which doesn't give me a headache , I came across this Scheme code (Hand typed, so parens may not match) :

(define fact-cps
    (lambda(n k)
        (cond
            ((zero? n) (k 1))
            (else
                (fact-cps (- n 1)
                          (lambda(v)
                             (k (* v n))))))))
(define fact
    (lambda(n)
        (fact-cps n (lambda(v)v))))  ;; (for giggles try (lambda(v)(* v 2)))

(fact 5) => 120

Great, but Scheme isn't Common Lisp, so I took a shot at it:

(defun not-factorial-cps(n k v)
    (declare (notinline not-factorial-cps)) ;; needed in clisp to show the trace
    (cond
        ((zerop n) (k v))
        ((not-factorial-cps (1- n) ((lambda()(setq v (k (* v n))))) v))))
       ;; so not that simple...
(defun factorial(n)
    (not-factorial-cps n (lambda(v)v) 1))

(setf (symbol-function 'k) (lambda(v)v))

(factorial 5) => 120

As you can see, I'm having some problems, so although this works, this has to be wrong. I think all I've accomplished is a convoluted way to do accumulator passing style. So other than going back to the drawing board with this, I had some questions: Where exactly in the Scheme example is the initial value for v coming from? Is it required that lambda expressions only be used? Wouldn't a named function accomplish more since you could maintain the state of each continuation in a data structure which can be manipulated as needed? Is there in particular style/way of continuation passing style in Common Lisp with or without all the macros? Thanks.

like image 285
macrolyte Avatar asked Feb 14 '23 07:02

macrolyte


1 Answers

The problem with your code is that you call the anonymous function when recurring instead of passing the continuation like in the Scheme example. The Scheme code can easily be made into Common Lisp:

(defun fact-cps (n &optional (k #'values))
  (if (zerop n) 
      (funcall k 1)
      (fact-cps (- n 1)
                (lambda (v)
                  (funcall k (* v n))))))

(fact-cps 10) ; ==> 3628800

Since the code didn't use several terms or the implicit progn i switched to if since I think it's slightly more readable. Other than that and the use of funcall because of the LISP-2 nature of Common Lisp it's the identical code to your Scheme version.

Here's an example of something you cannot do tail recursively without either mutation or CPS:

(defun fmapcar (fun lst &optional (k #'values))
  (if (not lst)
      (funcall k lst)
      (let ((r (funcall fun (car lst))))
        (fmapcar fun
                 (cdr lst)
                 (lambda (x)
                   (funcall k (cons r x)))))))

(fmapcar #'fact-cps '(0 1 2 3 4 5)) ; ==>  (1 1 2 6 24 120)

EDIT

Where exactly in the Scheme example is the initial value for v coming from?

For every recursion the function makes a function that calls the previous continuation with the value from this iteration with the value from the next iteration, which comes as an argument v. In my fmapcar if you do (fmapcar #'list '(1 2 3)) it turns into

;; base case calls the stacked lambdas with NIL as argument
((lambda (x) ; third iteration
   ((lambda (x) ; second iteration 
      ((lambda (x) ; first iteration
         (values (cons (list 1) x)))
       (cons (list 2) x)))
    (cons (list 3) x))
   NIL)

Now, in the first iteration the continuation is values and we wrap that in a lambda together with consing the first element with the tail that is not computed yet. The next iteration we make another lambda where we call the previous continuation with this iterations consing with the tail that is not computed yet.. At the end we call this function with the empty list and it calls all the nested functions from end to the beginning making the resulting list in the correct order even though the iterations were in oposite order from how you cons a list together.

Is it required that lambda expressions only be used? Wouldn't a named function accomplish more since you could maintain the state of each continuation in a data structure which can be manipulated as needed?

I use a named function (values) to start it off, however every iteration of fact-cps has it's own free variable n and k which is unique for that iteration. That is the data structure used and for it to be a named function you'd need to use flet or labels in the very same scope as the anonymous lambda functions are made. Since you are applying previous continuation in your new closure you need to build a new one every time.

Is there in particular style/way of continuation passing style in Common Lisp with or without all the macros?

It's the same except for the dual namespace. You need to either funcall or apply. Other than that you do it as in any other language.

like image 129
Sylwester Avatar answered Feb 28 '23 23:02

Sylwester