这种延续传递式Clojure函数生成器是如何工作的?
这是来自《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 否则,递归递
(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?
应用于1k
- 否则,
递归回到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