关于Clojure:如何在匿名Fn中进行递归,而无尾递归

How to do recursion in anonymous fn, without tail recursion

如何在不使用尾部递归的情况下在匿名函数中递归?

例如(摘自Vanderhart 2010,第38页):

1
2
3
4
5
(defn power
  [number exponent]
  (if (zero? exponent)
    1
    (* number (power number (- exponent 1)))))

假设我想作为匿名函数来执行此操作。由于某种原因,我不想使用尾递归。我该怎么办?例如:

1
2
( (fn [number exponent] ......))))) 5 3)
125

我可以为此使用循环,还是只能与recur一起使用?


fn特殊形式使您可以选择提供可在内部用于递归的名称。

1
2
(doc fn)
;=> (fn name? [params*] exprs*)

因此,添加" power"作为名称来完成您的示例。

1
2
3
4
(fn power [n e]
  (if (zero? e)
    1
    (* n (power n (dec e)))))

即使递归发生在尾部位置,也不会进行优化以替换当前的堆栈框架。 Clojure使用loop / recurtrampoline强制您对此进行明确说明。


我知道在Clojure中,语法支持"命名"一个匿名函数,正如其他答案所指出的那样。但是,我想展示一种解决该问题的第一原理方法,该方法不依赖于编程语言上是否存在特殊语法,并且可以在具有一阶过程(lambda)的任何语言上使用。

原则上,如果要进行递归函数调用,则需要引用该函数的名称,以使"匿名"(即,无名函数)不能用于执行递归……除非您使用Y组合器。这是它在Clojure中如何工作的解释。

让我通过示例向您展示它的用法。首先,Y-Combinator适用于具有可变参数数量的函数:

1
2
3
4
5
(defn Y [f]
  ((fn [x] (x x))
   (fn [x]
       (f (fn [& args]
              (apply (x x) args))))))

现在,实现问题中定义的power过程的匿名函数。显然,它没有名称,power只是最外层函数的参数:

1
2
3
4
5
(fn [power]
      (fn [number exponent]
          (if (zero? exponent)
              1
              (* number (power number (- exponent 1))))))

最后,这是将Y-Combinator应用于匿名power过程的方法,将它们作为参数number=5exponent=3(不是尾递归BTW)传递:

1
2
3
4
5
6
7
8
9
((Y
  (fn [power]
      (fn [number exponent]
          (if (zero? exponent)
              1
              (* number (power number (- exponent 1)))))))
 5 3)

> 125


fn带有一个可选的name参数,该参数可用于递归调用该函数。

例如:

1
2
3
4
5
6
user> ((fn fact [x]
           (if (= x 0)
               1
               (* x (fact (dec x)))))
       5)
;; ==> 120


是的,您可以使用looprecurloopfn s

中均适用

1
2
user> (loop [result 5 x 1] (if (= x 3) result (recur (* result 5) (inc x))))
125

一种偶氮型Clojure解决方案如下所示:

1
2
user> (reduce * (take 3 (repeat 5)))
125

或使用Math.pow();-)

1
2
user> (java.lang.Math/pow 5 3)
125.0


循环可以是重复出现的目标,因此您也可以这样做。