【问题标题】:Recursively run through a vector in Clojure递归遍历 Clojure 中的向量
【发布时间】:2014-04-17 00:36:10
【问题描述】:

我刚开始玩 Clojure。

如何遍历项目向量?

我的幼稚递归函数将具有类似于经典地图的形式,例如。

(defn map [f xs] (
  (if (= xs [])
      []
      (cons (f (first xs)) (map f (rest xs))
  )
))

问题是我在网上找不到任何此类代码的示例。我找到了很多使用内置序列遍历函数(如 for、map 和 loop)的示例。但是没有人做原始递归版本。

那是因为你不应该在 Clojure 中做这种事情吗? (例如,因为它使用了没有尾调用优化的低级 Java 原语?)?

【问题讨论】:

  • 您提到了尾调用优化,但请注意,Clojure 没有有尾调用优化,除非明确使用 recur
  • 不要重新定义map,它已经存在。查看(source map) 的顶部附近,您会看到与您编写的代码相同的代码,只是包裹在lazy-seq 中。如果您想要一个渴望版本,请使用mapv。您看不到很多显式递归的原因是有更高级别的函数,如 reducemap 等,它们以更具表现力的方式处理了许多用例。
  • ...所以答案是是的,你不应该在 Clojure 中做这种事情。顺便说一句,通过使用when-let,标准map 代码在空序列上隐式返回nil。当nil 被要求成为一个序列时,它伪装成一个空序列,因此恢复了顺序。这称为nil punning。相关摘录是(defn map [f coll] (lazy-seq (when-let [s (seq coll)] (cons (f (first s)) (map f (rest s))))))

标签: recursion clojure


【解决方案1】:

当您说“穿过向量”时,这是非常含糊的;由于 Clojure 是一个 lisp,因此专门从事序列分析和操作,使用这种语言的美妙之处在于您不会以“遍历向量然后对每个元素做某事”的术语来思考,而是您会更习惯地说“将其从向量中拉出”或“将此向量转换为 X”或“我希望此向量给我 X”。

正是由于 lisp 语言中的这种视角,您会看到如此多的示例和生产代码,这些示例和生产代码不仅在向量中循环/递归,而且还以一种简短、惯用的方式专门针对所需内容。使用 reduce map filter for into 等简单函数,您可以优雅地在矢量等序列上移动,同时同时你想要的内容。在大多数其他语言中,这至少是两个不同的部分:循环,然后是执行您想要的实际逻辑。

您经常会发现,如果您使用 C、C++、Java 等语言所获得的更具命令性的想法来考虑序列,那么您的代码(至少)比您使用其他语言时长大约 4 倍(至少)首先以更实用的方式考虑您的计划。

【讨论】:

    【解决方案2】:

    Clojure 仅在尾递归且仅在您使用显式 recur 调用时重用堆栈帧。其他一切都将消耗堆栈。上面的地图示例不是尾递归的,因为缺点发生在递归调用之后,因此它不能以任何语言进行 TCO。如果您将其切换为使用延续传递样式并使用对recur 的显式调用而不是map,那么您应该很高兴。

    【讨论】:

    • ... 或使用类似的东西(简化标准mapv):(defn mapv [f coll] (reduce (fn [v o] (conj v (f o))) [] coll))
    猜你喜欢
    • 1970-01-01
    • 2021-10-30
    • 2020-09-26
    • 1970-01-01
    • 2017-05-11
    • 2023-03-29
    • 2018-07-12
    • 2014-03-11
    • 1970-01-01
    相关资源
    最近更新 更多