Common Lisp中的持续传递风格?

时间:2014-04-05 22:30:03

标签: scheme lisp common-lisp conceptual

为了找到一个不会让我头疼的CPS的简单例子,我遇到了这个Scheme代码(Hand typed,所以parens可能不匹配):

(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

很好,但是Scheme 不是 Common Lisp,所以我对它进行了拍摄:

(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

正如您所看到的,我遇到了一些问题,所以虽然这有效,但必须 错误 。我认为我所取得的成就是一种令人费解的方式来做累积器传递方式。除了回到绘图板之外,我还有一些问题:在Scheme示例中, v 的初始值是从哪里来的?是必需只使用lambda表达式吗?命名函数不会完成更多,因为您可以维护数据结构中每个延续的状态,可以根据需要进行操作吗?在Common Lisp中是否有特定的样式/延续传递方式,有或没有所有的宏?感谢。

1 个答案:

答案 0 :(得分:7)

代码的问题在于,在重复发生时调用匿名函数,而不是像在Scheme示例中那样传递延续。 Scheme代码很容易变成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

由于代码没有使用多个术语或者隐式预测我切换到if,因为我认为它更具可读性。除此之外,由于Common Lisp的LISP-2性质而使用funcall,它与您的Scheme版本的代码相同。

这里有一个例子,你不能在没有变异或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)

修改

  

Scheme示例中的确切位置是v的初始值   从?

对于每次递归,函数都会生成一个函数,该函数使用来自此迭代的值调用前一个continuation,并使用下一次迭代的值,该值作为参数v。在fmapcar (fmapcar #'list '(1 2 3));; 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) 变为

n

现在,在第一次迭代中,continuation是值,我们将它包装在lambda中,同时使用尚未计算的尾部来构造第一个元素。下一次迭代我们创建了另一个lambda,我们用这个迭代调用前一个continuation来处理尚未计算的尾部。最后我们用空列表调用这个函数,它调用从结尾到开头的所有嵌套函数即使迭代按照你如何合并列表的相反顺序,也能以正确的顺序生成结果列表。

  

是否只需要使用lambda表达式?不会一个名字   功能完成更多,因为你可以保持每个的状态   数据结构的延续可以根据需要进行操作吗?

我使用一个命名函数(values)来启动它,但是fact-cps的每次迭代都有它自己的自由变量flet和k,这对于该迭代是唯一的。这是使用的数据结构,并且它是一个命名函数,您需要在与匿名lambda函数相同的范围内使用labelsfuncall。由于您在新的闭包中应用了先前的延续,因此每次都需要构建一个新的闭包。

  

是否有继续传递风格的特定风格/方式   包含或不包含所有宏的Common Lisp?

除了双命名空间之外,它是相同的。您需要apply或{{1}}。除此之外,您可以像使用任何其他语言一样。