一般函数可以通过调用自身来实现递归的效果,但这种方式会消耗栈有导致栈 溢出。比如下面这个计算阶乘的例子:
(defn recur-fac [n]
(if (= 1 n)
1
(* n (recur-fac (dec n)))))
通常,可以改写成尾递归的方式来避免消耗栈导致栈溢出,改写如下:
(defn recur-fac [n]
(letfn [(fac [c r]
(if (= 0 c)
r
(fac (dec c) (* c r))))]
(fac n 1)))
上面的写法使用了尾递归,在common lisp的语言中可以达到尾递归优化(TCO), 问题是clojure是基于JVM,无法支持完全的TCO,这主要是Java的安全模型决定 的。还好Clojue也支持同一个函数体直接调用自身的TCO,只是要使用clojure的 特殊形式,即使用recur关键字。改写如下:
(defn recur-fac [n]
(letfn [(fac [c r]
(if (= 0 c)
r
(recur (dec c) (* c r))))]
(fac n 1)))
好了,这样修改后,就不会有栈溢出的危险了。 上述例子是在同一个函数中进行自身调用,如果要在两个函数中相互调用则需要 用到trampoline,使用如下:
(declare is-odd?)
(defn is-even? [n]
(if (= n 0)
true
#(is-odd? (dec n))))
(defn is-odd? [n]
(if (= n 0)
false
#(is-even? (dec n))))
调用:
user=> (trampoline is-odd? 10000000)
false
user=> (trampoline is-even? 10000)
true