由于拉链和HOF,递归是一种气味(在惯用的Clojure中)吗?

时间:2015-03-15 09:12:50

标签: recursion clojure higher-order-functions idiomatic zipper

经典着作The Little LisperThe Little Schemer)基于两个重要的想法

  1. 您可以以递归方式解决大多数问题(而不是使用循环)(假设您有尾调用优化)
  2. Lisp很棒,因为它本身很容易实现。
  3. 现在人们可能会认为这适用于所有Lispy语言(包括Clojure)。麻烦的是,这本书是当时的人工制品(1989年),可能在Functional Programming之前,Higher Order Functions(HOFs)就是我们今天所拥有的。(或至少被认为适合本科生)。

    递归(至少部分)的好处是易于遍历嵌套数据结构,如('a 'b ('c ('d 'e)))

    example

    (def leftmost
      (fn [l]
        (println "(leftmost " l)
        (println (non-atom? l))
        (cond
          (null? l) '()
          (non-atom? (first l)) (leftmost (first l))
          true (first l))))
    

    现在使用Functional Zippers - 我们有一种非递归方法来遍历嵌套数据结构,并且可以像任何惰性数据结构一样遍历它们。对于example

    (defn map-zipper [m]
      (zip/zipper 
        (fn [x] (or (map? x) (map? (nth x 1))))
        (fn [x] (seq (if (map? x) x (nth x 1))))
        (fn [x children] 
          (if (map? x) 
            (into {} children) 
            (assoc x 1 (into {} children))))
        m))
    
    (def m {:a 3 :b {:x true :y false} :c 4})
    
    (-> (map-zipper m) zip/down zip/right zip/node)
    ;;=> [:b {:y false, :x true}]
    

    现在看来你可以用以下方法解决任何嵌套列表遍历问题:

    • 如上所述zipper,或
    • 一个zipper,它遍历结构并返回一组键,可让您使用assoc修改结构。

    假设:

    • 我当然假设数据结构固定大小,并且在遍历之前完全已知
    • 我排除了流数据源方案。

    我的问题是:由于拉链和HOF,递归是一种气味(在惯用的Clojure中)?

2 个答案:

答案 0 :(得分:3)

我会说,是的,如果您正在进行手动递归,您至少应该重新考虑是否需要。但我不会说拉链与此有任何关系。我对拉链的经验是它们具有理论用途,对Clojure新人来说非常令人兴奋,但是一旦掌握了一些东西,它们几乎没有实际价值,因为它们有用的情况非常罕见。

这真的是因为高阶函数已经为你实现了常见的递归模式,手动递归并不常见。但是,肯定不是你永远不应该使用手动递归的情况:它只是一个警告标志,暗示你可能会做其他事情。在我使用Clojure的四年中,我甚至无法回想起我实际需要拉链的情况,但我最终经常使用递归。

答案 1 :(得分:2)

Clojure成语阻止显式递归,因为调用堆栈是有限的:通常是大约10K深。 修改 Halloway&的第一个贝德拉的六项Clojure功能规划规则Programming Clojure(第89页)),

  

避免无限递归。 JVM无法优化递归调用   递归而不受约束的Clojure程序将会破坏它们的堆栈。

有几种缓解措施:

  • recur处理尾递归。
  • 延迟序列可以将深度调用堆栈转换为浅层调用堆栈 跨越一个展开的数据结构。许多HOF在序列中 库(例如mapfilter)执行此操作。