这种延续传递式Clojure函数生成器是如何工作的?

这种延续传递式Clojure函数生成器是如何工作的?,clojure,continuations,continuation-passing,Clojure,Continuations,Continuation Passing,这是来自《Clojure的欢乐》,第二版 然后做一个阶乘: (def fac (mk-cps zero? identity #(* %1 %2))) 我的理解是: mm cps生成一个接受n的函数,fn[n] 内部函数fn[n k]最初是用n和kend调用的 连续函数cont[v]被定义为(调用k,部分应用kont,其中v)作为第一个参数,n作为第二个参数。为什么要用partial而不是简单地(k(cont v n))来编写 如果accept?函数通过,则完成递归,将k应用于1 否则,递归递

这是来自《Clojure的欢乐》,第二版

然后做一个阶乘:

(def fac (mk-cps zero? identity #(* %1 %2)))
我的理解是:

  • mm cps生成一个接受n的函数,fn[n]
  • 内部函数fn[n k]最初是用nkend调用的
  • 连续函数cont[v]被定义为(调用k,部分应用kont,其中v)作为第一个参数,n作为第二个参数。为什么要用
    partial
    而不是简单地
    (k(cont v n))
    来编写
  • 如果
    accept?
    函数通过,则完成递归,将
    k
    应用于1
  • 否则,
    递归
    递归回到fn[n k],使用递减的n和延续函数
  • 总之,kont不会改变
直到最后的
(k1)
,才真正执行
k
,我说得对吗?
因此,
(fac 3)
在被评估之前首先被扩展到
(*1(*23))

我没有这本书,但我假设激励的例子是

(defn fact-n [n]
  (if (zero? n)
      1
      (* n (recur (dec n)))))

;=> CompilerException: Can only recur from tail position
最后一种形式必须写成
(*n(fact-n(dec n))
,而不是尾部递归。问题是递归之后还有一些事情要做,即乘以
n

(defn gen-cps [accept? c k]
  (fn [n]
    (loop [n n, k k]
      (if (accept? n)
          (k 1)
          (recur (dec n) (comp k (partial c n)))))))
接续传球的方式就是把球翻过来。在递归调用返回后,不应用当前上下文/延续的剩余内容,而是将上下文/延续传递到递归调用中,以便在完成时应用。我们不是隐式地将连续性存储在堆栈上作为调用帧,而是通过函数组合显式地累积它们

在本例中,我们向阶乘添加了一个额外的参数
k
,该函数执行递归调用返回后的操作

(defn fact-nk [n k]
  (if (zero? n)
      (k 1)
      (recur (dec n) (comp k (partial * n)))))
第一个
k
输入是最后一个输出。最后,我们只想返回计算出的值,因此中的第一个
k
应该是标识函数

以下是基本情况:

(fact-nk 0 identity)
;== (identity 1)
;=> 1
下面是
n=3

(fact-nk 3 identity)
;== (fact-nk 2 (comp identity (partial * 3)))
;== (fact-nk 1 (comp identity (partial * 3) (partial * 2)))
;== (fact-nk 0 (comp identity (partial * 3) (partial * 2) (partial * 1)))
;== ((comp identity (partial * 3) (partial * 2) (partial * 1)) 1)
;== ((comp identity (partial * 3) (partial * 2)) 1)
;== ((comp identity (partial * 3)) 2)
;== ((comp identity) 6)
;== (identity 6)
;=> 6
与非尾部递归版本相比

(fact-n 3)
;== (* 3 (fact-n 2))
;== (* 3 (* 2 (fact-n 1)))
;== (* 3 (* 2 (* 1 (fact-n 0))))
;== (* 3 (* 2 (* 1 1)))
;== (* 3 (* 2 1))
;== (* 3 2)
;=> 6
现在,为了使这一点更加灵活,我们可以将
zero?
*
分解出来,并将它们改为变量参数

第一种方法是

(defn cps-anck [accept? n c k]
  (if (accept? n)
      (k 1)
      (recur accept?, (dec n), c, (comp k (partial c n)))))
但是既然
accept?
c
没有改变,我们就可以把它们去掉,转而使用内部匿名函数。Clojure对此有一种特殊的形式,
loop

(defn cps-anckl [accept? n c k]
  (loop [n n, k k]
    (if (accept? n)
        (k 1)
        (recur (dec n) (comp k (partial c n))))))
最后,我们可能想把它变成一个函数生成器,它可以拉入
n

(defn gen-cps [accept? c k]
  (fn [n]
    (loop [n n, k k]
      (if (accept? n)
          (k 1)
          (recur (dec n) (comp k (partial c n)))))))
这就是我编写mk-cps的方法(注意:最后两个参数颠倒了)


谢谢你把它拼出来!我认为这本书在这方面有太多的“魔力”。
(def factorial (gen-cps zero? * identity))
(factorial 5)
;=> 120

(def triangular-number (gen-cps #{1} + identity))    
(triangular-number 5)
;=> 15