Clojure:减less与适用

我理解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? 这样或那样有很大的不同吗? 从我的(有限)性能testing来看, reduce速度似乎有点快。

当然,对于关联函数来说, reduceapply当然只是等价的(就返回的最终结果而言),关联函数需要在可变参数的情况下查看所有参数。 当他们在结果上是相当的时候,我会说, apply总是完美的惯用,而reduce是等价的,并可能在许多常见的情况下,眨眼间。 接下来是我相信这个理由。

+本身是以可变参数大小(多于2个参数)的forms实现的。 事实上,这似乎是一种非常明智的“默认”方法,可以用于任何可变的联想function: reduce有可能通过一些优化来加快速度 – 可能是通过像internal-reduce这样的东西,但是希望能够在将来重新引入 – 在可变参数的情况下复制每一个可能从中受益的function是愚蠢的。 在这种常见的情况下, apply只会增加一点开销。 (注意,没有什么可担心的。)

另一方面,一个复杂的函数可能会利用一些不够普遍的优化机会, 那么apply会让你利用这些,而reduce可能会减慢你的速度。 后者在实践中出现的一个很好的例子是由str提供的:它在内部使用了一个StringBuilder并且将从使用apply而不是reduce显着受益。

所以,如果有疑问,我会说使用apply 。 如果你碰巧知道它不会为了reduce而购买任何东西(而且这种情况不太可能会很快发生变化),如果你愿意的话,可以使用reduce来减less不必要的开销。

对于看这个答案的新手来说,
要小心,他们不一样:

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

意见各不相同 – 在更大的Lisp世界中, reduce绝对被认为更加习惯。 首先,已经讨论了各种各样的问题。 此外,一些Common Lisp编译器在apply非常长的列表时,实际上会失败,因为它们如何处理参数列表。

在我的圈子中的Clojurists,虽然在这种情况下使用apply似乎更常见。 我觉得更容易沟通,也喜欢它。

这种情况并没有什么不同,因为+是一个特殊的情况,可以适用于任何数量的参数。 Reduce是一种将期望固定数量的参数(2)的函数应用于任意长的参数列表的方法。

我通常发现自己宁愿减less在任何types的收集行为 – 它performance良好,并且是一个相当有用的function。

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

 (apply + 1 2 other-number-list) 

当使用像+这样的简单函数时,使用哪一个函数并不重要。

一般来说,减less是一个累积的操作。 您将当前累计值和一个新值显示给累加函数,其结果是下一次迭代的累计值。 所以,你的迭代看起来像:

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

对于应用,这个想法是,你正试图调用一个函数,期望一些标量参数,但他们目前在一个集合,需要被拉出。 所以,而不是说:

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

我们可以说:

 (apply some-fn vals) 

它被转换成相当于:

 (some-fn val1 val2 val3) 

因此,使用“apply”就像在序列周围“去除括号”一样。

在这个特定的情况下,我更喜欢reduce因为它更具可读性 :当我阅读

 (reduce + some-numbers) 

我马上知道你正在把一个序列变成一个价值。

通过apply我必须考虑应用哪个function:“啊,这是+function,所以我得到…一个单一的数字”。 略less直截了当。

在这个话题上迟了一点,但是我在阅读这个例子后做了一个简单的实验。 这里是我的repl的结果,我只是不能从响应中推论出任何东西,但似乎在reduce和apply之间存在某种caching。

 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的源代码减less其相当干净的recursion与内部减less,但没有发现任何应用的实施。 Clojure实现+用于内部调用reduce,这是通过replcaching的,这似乎解释了第四个调用。 有人能澄清这里真的发生了什么吗?