Recursive Clojure

一般函数可以通过调用自身来实现递归的效果,但这种方式会消耗栈有导致栈 溢出。比如下面这个计算阶乘的例子:

(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

blogroll

social