我理解 reduce和 apply在概念上的不同:
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)
然而,哪一个是更加成语化的咒语呢?这两者之间有什么区别吗?从我(有限的)性能测试来看,似乎 reduce更快一些。
在这种情况下没有区别,因为 + 是一种特殊情况,可以应用于任意数量的参数。Reduce 是一种应用函数的方法,该函数期望将固定数量的参数(2)应用于任意长的参数列表。
我通常发现自己更喜欢在任何类型的集合上执行 reduce ——它执行得很好,总的来说是一个非常有用的功能。
我使用 application 的主要原因是,如果参数在不同的位置表示不同的内容,或者如果您有两个初始参数,但希望从集合中获得其余内容,例如。
(apply + 1 2 other-number-list)
reduce和 apply当然只是等价的(就最终返回的结果而言)关联函数,它们需要在变量情况下看到它们的所有参数。当它们在结果方面是等价的时候,我会说 apply总是完全习惯的,而 reduce在许多常见的情况下是等价的——并且可能会减少一小部分眨眼的时间。以下是我相信这一点的理由。
+本身是根据 reduce实现的,用于可变参数情况(超过2个参数)。事实上,这似乎是一个非常明智的“默认”方式去任何可变性,关联函数: reduce有潜力执行一些优化,以加速事情——也许通过像 internal-reduce这样的东西,一个1.2的新颖性最近在主禁用,但有希望在未来重新引入——这将是愚蠢的复制在每一个功能,可能受益于他们在 vararg 的情况下。在这种常见的情况下,apply只会增加一点开销。(注意,这没什么好担心的。)
+
internal-reduce
另一方面,一个复杂的函数可能会利用一些优化机会,这些优化机会不足以构建到 reduce中; 然后 apply会让你利用这些优化机会,而 reduce实际上可能会减慢你的速度。在实践中出现的后一种情况的一个很好的例子是由 str提供的: 它在内部使用 StringBuilder,并将从使用 apply而不是 reduce中受益匪浅。
str
StringBuilder
所以,我建议在有疑问的时候使用 apply; 如果你碰巧知道它不会给你买任何超过 reduce的东西(而且这不太可能很快改变) ,如果你喜欢的话,可以随意使用 reduce来减少那些不必要的开销。
人们的看法各不相同——在更大的 Lisp 世界中,reduce肯定被认为更加地道。首先,有一些已经讨论过的可变问题。此外,一些 Common Lisp 编译器实际上会失败,当 apply被应用到非常长的列表,因为他们如何处理参数列表。
然而,在我的圈子里,在这种情况下使用 apply似乎更为常见。我发现它更容易理解,也喜欢它。
对于看到这个答案的新手来说, 小心,它们是不一样的:
(apply hash-map [:a 5 :b 6]) ;= {:a 5, :b 6} (reduce hash-map [:a 5 :b 6]) ;= \{\{{:a 5} :b} 6}
在这个特定的情况下,我更喜欢 reduce,因为它更 可读: 当我阅读
(reduce + some-numbers)
我马上就知道你正在把一个序列转换成一个值。
对于 apply,我必须考虑应用哪个函数: “啊,这是 +函数,所以我得到的是... ... 一个数字”。没那么直接。
当使用像 + 这样的简单函数时,使用哪个函数并不重要。
一般来说,reduce是一种累积操作。将当前的累积值和一个新值提供给累积函数。该函数的结果是下一次迭代的累积值。因此,您的迭代看起来像:
cum-val[i+1] = F( cum-val[i], input-val[i] ) ; please forgive the java-like syntax!
对于 application,其思想是您试图调用一个期望有大量标量参数的函数,但是它们当前位于一个集合中,需要被提取出来。所以,与其说:
vals = [ val1 val2 val3 ] (some-fn (vals 0) (vals 1) (vals 2))
我们可以说:
(apply some-fn vals)
转换为等同于:
(some-fn val1 val2 val3)
因此,使用“ application”就像在序列周围“移除括号”一样。
这个话题有点晚了,但是在阅读了这个例子之后,我做了一个简单的实验。这是我的回复的结果,我只是不能从回复中推断出任何东西,但似乎在 reduce 和 application 之间有某种缓存启动。
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 实现了相当干净的递归,但是在 application 的实现上没有发现任何东西。Clojure 的实现 + for application inside 內部調用減少,它被 repl 所缓存,這似乎解釋了第四個調用。有人能解释一下到底发生了什么吗?
这个函数(在本例中是 +)可以应用于由带有结束集合的前置中间参数形成的参数列表。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)
有点晚了,但是..。
在这种情况下,没有很大的区别。但总的来说,它们是不等价的。进一步降低可以更有效。为什么?
如果集合或类型实现 减少接口,则应减少检查。这意味着类型知道如何以最高性能的方式向降低函数提供它的值。 可以通过返回“还原”值来提前停止。
另一方面,由 ApplyToHelper调用。它通过计算参数,从集合中解包值来分配到正确的参数。
这对性能有很大影响吗? 可能没有。 我的观点正如其他人已经指出的那样。如果希望从语义上将集合“减少”为单个值,请使用 reduce。否则使用适用。