首页 文章

Clojure:减少与应用

提问于
浏览
115

我理解 reduceapply 之间的概念差异:

(reduce + (list 1 2 3 4 5))
; translates to: (+ (+ (+ (+ 1 2) 3) 4) 5)

(apply + (list 1 2 3 4 5))
; translates to: (+ 1 2 3 4 5)

然而,哪一个更惯用的clojure?这种方式或其他方式有什么不同吗?从我的(有限的)性能测试来看,似乎 reduce 有点快 .

9 回答

  • 14

    reduceapply 当然只对于需要在变量arity情况下查看所有参数的关联函数而言是等效的(就返回的最终结果而言) . 当它们在结果方面是等价的时候,我会说 apply 总是完全惯用的,而 reduce 在很多常见情况下都是等效的 - 并且可能会在很短的时间内完成 . 以下是我相信这一点的理由 .

    + 本身就 reduce 实现了变量arity案例(超过2个参数) . 事实上,对于任何变量,关联函数来说,这似乎是一种非常明智的方法: reduce 有可能执行一些优化以加快速度 - 可能通过最近在主人身上禁用的1.2新奇事物 internal-reduce ,但是希望将来能够重新引入 - 在vararg案例中,在每个可能从中受益的函数中复制将是愚蠢的 . 在这种常见情况下, apply 只会增加一点开销 . (注意没什么好担心的 . )

    另一方面,复杂的函数可能会利用一些优化机会,这些机会不够通用,无法构建到_628911中;然后 apply 会让你利用那些,而 reduce 可能会让你失望 . 在实践中出现的后一种情况的一个很好的例子是由 str 提供的:它在内部使用了 StringBuilder ,并且将从使用 apply 而不是 reduce 中获益 .

    所以,如果有疑问,我会说 apply ;如果你碰巧知道它不会给你带来任何超过 reduce 的东西(并且这不太可能很快改变),如果你愿意,可以随意使用 reduce 来减少这种微小的不必要的开销 .

  • 7

    对于看这个答案的新手,
    小心,它们不一样:

    (apply hash-map [:a 5 :b 6])
    ;= {:a 5, :b 6}
    (reduce hash-map [:a 5 :b 6])
    ;= {{{:a 5} :b} 6}
    
  • 4

    意见各不相同 - 在更大的Lisp世界中, reduce 绝对被认为是更惯用的 . 首先,已经讨论过可变问题 . 此外,当 apply 应用于非常长的列表时,由于它们如何处理参数列表,一些Common Lisp编译器实际上会失败 .

    然而,在我的圈子中的Clojurists中,在这种情况下使用 apply 似乎更常见 . 我发现它更容易理解并且也更喜欢它 .

  • 9

    它在这种情况下没有区别,因为它是一个特殊情况,可以应用于任意数量的参数 . Reduce是一种将需要固定数量的参数(2)的函数应用于任意长的参数列表的方法 .

  • 116

    我通常发现自己更倾向于使用reduce来处理任何类型的集合 - 它表现良好,并且通常是非常有用的功能 .

    我将使用apply的主要原因是,如果参数在不同位置意味着不同的东西,或者如果你有几个初始参数但是想要从集合中获取其余参数,例如

    (apply + 1 2 other-number-list)
    
  • 3

    在这种特殊情况下,我更喜欢 reduce ,因为它更多 readable :当我读到时

    (reduce + some-numbers)
    

    我立即知道你正在将一个序列变成一个值 .

    使用 apply 我必须考虑应用了哪个函数:“啊,它是 + 函数,所以我得到......一个数字” . 略显简单明了 .

  • 3

    当使用类似的简单函数时,使用哪一个并不重要 .

    一般来说,想法是减少是一种累积操作 . 您将当前累积值和一个新值呈现给累积函数,结果是下一次迭代的累积值 . 所以,你的迭代看起来像:

    cum-val[i+1] = F( cum-val[i], input-val[i] )    ; please forgive the java-like syntax!
    

    对于apply,我们的想法是你试图调用一个期望一些标量参数的函数,但它们目前在一个集合中,需要被拔出 . 所以,而不是说:

    vals = [ val1 val2 val3 ]
    (some-fn (vals 0) (vals 1) (vals2))
    

    我们可以说:

    (apply some-fn vals)
    

    它被转换为等同于:

    (some-fn val1 val2 val3)
    

    因此,使用“apply”就像是在序列周围“删除括号” .

  • 18

    关于这个话题稍晚但我在阅读这个例子后做了一个简单的实验 . 这是我的repl的结果,我不能从响应中推断出任何东西,但似乎在reduce和apply之间存在某种缓存 .

    user=> (time (reduce + (range 1e3)))
    "Elapsed time: 5.543 msecs"
    499500
    user=> (time (apply + (range 1e3))) 
    "Elapsed time: 5.263 msecs"
    499500
    user=> (time (apply + (range 1e4)))
    "Elapsed time: 19.721 msecs"
    49995000
    user=> (time (reduce + (range 1e4)))
    "Elapsed time: 1.409 msecs"
    49995000
    user=> (time (reduce + (range 1e5)))
    "Elapsed time: 17.524 msecs"
    4999950000
    user=> (time (apply + (range 1e5)))
    "Elapsed time: 11.548 msecs"
    4999950000
    

    查看clojure的源代码,使用internal-reduce减少其非常干净的递归,虽然没有找到任何关于apply的实现 . Clojure实现for apply内部调用reduce,它由repl缓存,似乎解释了第4次调用 . 有人可以澄清这里真的发生了什么吗?

  • 43

    申请的优点是给定功能(在这种情况下)可以应用由预先存在的干预参数与结束集合形成的参数列表 . Reduce是一个抽象来处理为每个函数应用函数的集合项,并且不适用于变量args情况 .

    (apply + 1 2 3 [3 4])
    => 13
    (reduce + 1 2 3 [3 4])
    ArityException Wrong number of args (5) passed to: core/reduce  clojure.lang.AFn.throwArity (AFn.java:429)
    

相关问题