clojure recur

Thu 15 July 2010
  • 手艺 tags:
  • clojure published: true comments: true

clojure是函数是编程语言,本身没有循环的语法。要实现类似循环的效果,需要用递归来实现。比如计算从1加到n的和这样一个函数:

[cc lang="lisp"]
(defn addup
([limit] (addup limit 0 0))
([limit current sum]
(if (< limit current)
sum
(addup limit (+ 1 current) (+ current sum)))))
[/cc]

调用

(addup 10)

可以得到55

但是调用

(addup 50000)

不出意外,出现了StackOverflowException, 这是递归典型的错误。

为此,需要进行优化。尾递归优化是指如果递归满足一定条件,编译器可以将递归展开,避免出现栈溢出的问题。这里的条件,就是递归的最后一次调用是否是其本身,比如上面的代码,最后一次操作仍然是addup,即满足尾递归的条件,而如果同样的代码写成:

[cc lang="lisp"]
(defn addup2
([limit] (addup2 limit 0))
([limit current]
(if (< limit current)
0
(+ current (addup2 limit (+ current 1))))))[/cc]

最后一个操作是+,不满足尾递归循环,无法进行优化。

clojure默认不进行尾递归优化,需要使用关键字recur来保证优化,把第一段代码写成:

[cc lang="lisp"]
(defn addup
([limit] (addup limit 0 0))
([limit current sum]
(if (< limit current)
sum
(recur limit (+ 1 current) (+ current sum)))))
[/cc]

社区的解释是,如果程序员在无法进行尾递归优化的代码中使用recur会发生报错,这样有助于他们发现代码中潜在的缺陷。

在第二段代码中使用recur,会得到异常:

java.lang.UnsupportedOperationException: Can only recur from tail position

此外,clojure提供loop函数简化书写:

[cc lang="lisp"]
(defn addup3
([limit]
(loop [current 0 sum 0]
(if (< limit current)
sum
(recur (+ current 1) (+ current sum))))))
[/cc]

这种写法同样可以应用尾递归优化而不会导致栈溢出。

The post is brought to you by lekhonee v0