Clojure:尾部位置究竟是什么?

时间:2011-10-18 20:47:23

标签: clojure

什么是“尾部位置”的精确定义,以便在clojure中重复出现。我认为这将是循环S表达式中的最后一项,但在下面的例子中,在我看来,以(if ...)开头的S-Expression处于尾部位置即([LOOP KEYWORD] [BINDING STATMENTS] [IF声明])。

(= __
  (loop [x 5
         result []]
    (if (> x 0)
      (recur (dec x) (conj result (+ 2 x)))
      result)))

代码来自http://www.4clojure.com/problem/68

密切相关的问题:How can I call recur in an if conditional in Clojure?

2 个答案:

答案 0 :(得分:57)

尾部位置是表达式将从中返回值的位置。在评估尾部位置的表格后,不再评估表格。

The Joy of Clojure

考虑这个例子
(defn absolute-value [x]
  (if (pos? x)
      x        ; "then" clause 
      (- x)))  ; "else" clause

它需要一个参数并将其命名为x。如果x已经是正数,则x为 回;否则返回x的反面。 if形式在函数的尾部位置,因为无论它返回什么,整个 功能会返回。 “then”子句中的x也位于函数的尾部位置。 但是“else”子句中的x不在函数的尾部位置,因为x的值 传递给 - 函数,不直接返回。 else子句作为一个整体(-x)在 尾巴位置。

同样在表达式

(if a
    b
    c)

bc都位于尾部位置,因为它们中的任何一个都可以从if语句返回。

现在在你的例子中

(loop [x 5
       result []]
  (if (> x 0)
    (recur (dec x) (conj result (+ 2 x)))
    result)))

(if ...)表单位于(loop ...)表单的尾部位置,(recur ...)表单和result表单都位于{{1}的尾部位置形式。

另一方面,您链接的问题

(if ...)

(fn [coll] (let [tail (rest coll)] (if (empty tail) 1 (+ 1 (recur tail))))) 在尾部位置,因为recur将在(+ 1 ...)之后进行评估。因此Clojure编译器会出错。

尾部位置非常重要,因为您可以从尾部位置使用(recur tail)形式。函数式编程语言通常使用递归来处理程序编程语言通过循环完成的任务。但递归是有问题的,因为它消耗堆栈空间并且深度递归可能导致堆栈溢出问题(除了缓慢)。这个问题通常通过尾调用优化(TCO)解决,当递归调用发生在函数/表单的尾部位置时,它会取消调用者。

因为Clojure托管在JVM上并且JVM不支持尾调用优化,所以需要一个技巧来进行递归。 recur形式就是这个技巧,它允许Clojure编译器执行类似尾部调用优化的操作。此外,它验证recur确实处于尾部位置。好处是您可以确保优化确实发生。

答案 1 :(得分:24)

为了补充保罗上面的优秀答案, Clojure的喜悦(ed1)提供了一个表格(表7.1),它准确显示尾部位置在各种形式/表达中的位置,我是转载如下。查找每个表单/表达式中出现“tail”字样的位置:

|---------------------+-------------------------------------------+---------------|
| Form                | Tail Position                             | recur target? |
|---------------------+-------------------------------------------+---------------|
| fn, defn            | (fn [args] expressions tail)              | Yes           |
| loop                | (loop [bindings] expressions tail)        | Yes           |
| let, letfn, binding | (let [bindings] expressions tail)         | No            |
| do                  | (do expressions tail)                     | No            |
| if, if-not          | (if test then tail else tail)             | No            |
| when, when-not      | (when test expressions tail)              | No            |
| cond                | (cond test test tail ... :else else tail) | No            |
| or, and             | (or test test ... tail)                   | No            |
| case                | (case const const tail ... default tail)  | No            |
|---------------------+-------------------------------------------+---------------|