Clojure:减less与适用
我理解reduce
和apply
的概念区别:
(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
速度似乎有点快。
当然,对于关联函数来说, reduce
和apply
当然只是等价的(就返回的最终结果而言),关联函数需要在可变参数的情况下查看所有参数。 当他们在结果上是相当的时候,我会说, 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的,这似乎解释了第四个调用。 有人能澄清这里真的发生了什么吗?