我没有这本书,但我认为激励的例子是
(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。
延续传递风格所做的就是把它翻过来。在递归调用返回后,不要应用当前上下文/延续的剩余部分,而是将上下文/延续传递给递归调用以在完成时应用。我们没有将延续隐式存储在堆栈中作为调用帧,而是通过函数组合显式累积它们。
在这种情况下,我们向阶乘添加了一个额外的参数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