循环的尾部位置到底是什么?

发布于 2024-12-10 09:23:32 字数 568 浏览 0 评论 0原文

Clojure 中 recur 的“尾部位置”的准确定义是什么?我认为这将是循环 S 表达式中的最后一项,但在下面的示例中,在我看来,以 (if ...) 开头的 S 表达式位于尾部位置即(循环[绑定语句][if 语句])。

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

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

密切相关问题: 如何调用 recur在 Clojure 的 if 条件中?

What's the precise definition of "tail position" for recur in Clojure? I would think that it would be the last item in a loop S-expression, but in the example below it seems to me that the S-Expression which starts with (if ...) is in tail position i.e. (loop [binding statements] [if statement]).

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

(Code taken from http://www.4clojure.com/problem/68)

Closely related question: How can I call recur in an if conditional in Clojure?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

汐鸠 2024-12-17 09:23:32

尾部位置是表达式返回值的位置。评估尾部位置的表单后,不再评估任何表单。

请考虑 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 表单位于 (if ...) 表单的尾部位置。

另一方面,在您链接的问题中,

(fn [coll] (let [tail (rest coll)]
             (if (empty tail)
                 1
                 (+ 1 (recur tail)))))

recur not 位于尾部位置,因为 (+ 1 ...) 将被评估在(recur tail)之后。因此 Clojure 编译器会给出错误。

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

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

The tail position is a position which an expression would return a value from. There are no more forms evaluated after the form in the tail position is evaluated.

Consider this example from The Joy of Clojure

(defn absolute-value [x]
  (if (pos? x)
      x        ; "then" clause 
      (- x)))  ; "else" clause

It takes a single parameter and names it x. If x is already a positive number, then x is
returned; otherwise the opposite of x is returned.
The if form is in the function’s tail position because whatever it returns, the whole
function will return. The x in the “then” clause is also in a tail position of the function.
But the x in the “else” clause is not in the function’s tail position because the value of x
is passed to the - function, not returned directly. The else clause as a whole (- x) is in
a tail position.

Similarly in the expression

(if a
    b
    c)

both b and c are in tail positions, because either of them could be returned from the if statement.

Now in your example

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

the (if ...) form is in the tail position of the (loop ...) form and both the (recur ...) form and the result form are in the tail position of the (if ...) form.

On the other hand in the question that you linked

(fn [coll] (let [tail (rest coll)]
             (if (empty tail)
                 1
                 (+ 1 (recur tail)))))

the recur is not in tail position because the (+ 1 ...) will be evaluated after the (recur tail). Therefore the Clojure compiler gives an error.

Tail position is important because you can use the recur form from tail position. Functional programming languages usually use recursion for what procedural programming languages accomplish by loops. But recursion is problematic, because it consumes stack space and deep recursion can lead to stackoverflow problems (in addition to being slow). This problem is usually solved by tail call optimization (TCO), which does away with the caller when the recursive call happens in the tail position of a function / form.

Because Clojure is hosted on the JVM and the JVM does not support tail call optimization, it needs a trick to do recursion. The recur form is that trick, it allows the Clojure compiler to do something similar to tail call optimization. In addition, it verifies that recur is indeed in a tail position. The benefit is that you can make sure that the optimization actually does happen.

撩起发的微风 2024-12-17 09:23:32

只是为了补充上面 Paul 的精彩答案,The Joy of 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            |
|---------------------+-------------------------------------------+---------------|

Just to supplement the excellent answer from Paul above, The Joy of Clojure (ed1) provides a table (Table 7.1) that shows exactly where tail position is in various forms/expressions, which I've reproduced below. Look for where the word "tail" occurs in each form/expression:

|---------------------+-------------------------------------------+---------------|
| 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            |
|---------------------+-------------------------------------------+---------------|
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文