Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Recursion Clojure:只能从尾部位置复发_Recursion_Clojure - Fatal编程技术网

Recursion Clojure:只能从尾部位置复发

Recursion Clojure:只能从尾部位置复发,recursion,clojure,Recursion,Clojure,我试图递归地反转列表,但我得到的只能在运行时从尾部位置重复出现。这到底意味着什么?如何改进我的代码使其工作 (defn recursive-reverse [coll] (loop [coll coll] (if (< (count coll) 2) '(coll) (conj (first coll) (recur (rest coll))) ))) 在Clojure中,只能从尾部位置调用recur。它是语言设计的一部分,是与JVM相关的限制 你可以

我试图递归地反转列表,但我得到的
只能在运行时从尾部位置重复出现。这到底意味着什么?如何改进我的代码使其工作

(defn recursive-reverse [coll]
  (loop [coll coll]
    (if (< (count coll) 2) '(coll)
      (conj (first coll) (recur (rest coll)))
      )))

在Clojure中,只能从尾部位置调用recur。它是语言设计的一部分,是与JVM相关的限制

你可以调用你的函数名(使用递归)而不使用递归,这取决于你如何构造你的程序,比如你是否使用惰性序列,你可能不会让堆栈爆炸。但是最好使用recur,使用带有recur的循环允许一些本地绑定

下面是一个使用递归而不重复的示例

(fn flt [coll]
  (let [l (first coll) r (next coll)]
    (concat 
      (if (sequential? l)
        (flt l)
        [l])
      (when (sequential? r)
        (flt r)))))

错误
只能从尾部位置重复出现
意味着您没有调用
recur
作为函数递归部分的最后一个表达式-事实上,在您的代码中,
conj
是最后一个表达式

使代码正常工作的一些改进:

  • 询问集合的基本大小写是否为空,而不是比较其长度是否小于2
  • conj
    接收其第一个参数的集合,而不是元素
  • 最好使用
    cons
    而不是
    conj
    (这会根据集合的具体类型,在不同的位置添加新元素)。这样,如果输入集合是列表或向量,则返回的集合将被反转(尽管返回集合的类型始终为
    clojure.lang.Cons
    ,无论输入集合的类型如何)
  • 请注意,
    ”(coll)
    是一个包含单个元素(符号
    coll
    )的列表,而不是实际的集合
  • 为了正确地反转列表,您需要迭代输入列表并将每个元素附加到输出列表的开头;为此使用累加器参数
  • 利用函数尾部位置的尾部递归调用
    recur
    ;这样,每次递归调用都会占用固定的空间,堆栈不会无限增长
我相信这就是你的目标:

(defn recursive-reverse [coll]
  (loop [coll coll
         acc  (empty coll)]
        (if (empty? coll)
            acc
            (recur (rest coll) (cons (first coll) acc)))))

使代码工作的最简单方法是使用函数名,而不是
recur

(defn recursive-reverse [coll]
  (loop [coll coll]
    (if (< (count coll) 2) '(coll)
      (conj (first coll) (recursive-reverse (rest coll)))
      )))
它从输入列表的头部提取项目,并将其添加到累加器列表的头部


“Tail position”仅表示函数返回前的最后一件事是
recur
。这与“自然递归”解决方案不同,在这种解决方案中,函数在调用自身和返回之间仍有工作要做。

为什么使用
letfn
而不是带有两个参数的
loop
?使用
loop
更惯用。@Gert:这只是另一种方法,我想如果OP不熟悉loop,那么letfn可能更有意义。惯用的判断有来源吗?实际上,我的理解是,无论如何,用累加器写这篇文章都不是惯用的(基于《Clojure的快乐》中的某些东西)。这是一个好观点。但是
循环
正是针对这种情况而存在的(创建了一个递归点,而不是函数调用本身),它也更简洁、更广泛地使用,并且在感谢您的精彩回答时进行了说明/记录。新的谜团:为什么这适用于向量而不适用于列表?我在问题中发布了我的
leon repl
输出。这是因为工作方式:“根据具体类型,“添加”可能发生在不同的“位置”。如果我们将
conj
更改为
cons
(正如我刚才在回答中所做的那样),返回的序列将始终颠倒,但集合的类型将是
clojure.lang.cons
。请看下面的解释它对列表和向量的作用不同,因为
conj
对两者的作用不同。将项目“添加”到列表的前面和向量的末尾是最便宜的
conj
查看收藏类型并以最便宜的方式添加。老实说,我从来没有觉得这是对的,但我相信Rich Hickey和其他Clojure开发人员已经对此进行了深入的思考,并决定利大于弊。我猜表演是他们最基本的操作,但是的,这似乎很奇怪。我更新了我的答案,如果输入是向量,我宁愿返回不同类型的正确答案,而不是错误答案
(defn recursive-reverse [coll]
  (loop [coll coll]
    (if (< (count coll) 2) '(coll)
      (conj (first coll) (recursive-reverse (rest coll)))
      )))
(defn recursive-reverse [s]
  (letfn [
    (my-reverse [s c]
      (if (empty? s)
        c
        (recur (rest s) (conj c (first s)))))]
    (my-reverse s '())))