Clojure:减少与应用

时间:2010-06-30 21:04:35

标签: clojure

我理解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 个答案:

答案 0 :(得分:118)

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

对于变量arity案例(超过2个参数),

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

另一方面,复杂的函数可能会利用一些优化机会,这些机会不够通用,无法构建到reduce中;然后apply会让你利用那些reduce可能实际上减慢你的速度。 str提供了实际发生的后一种情况的一个很好的例子:它在内部使用StringBuilder,并且会因使用apply而非reduce而受益匪浅。< / p>

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

答案 1 :(得分:48)

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

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

答案 2 :(得分:20)

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

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

答案 3 :(得分:19)

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

答案 4 :(得分:9)

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

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

(apply + 1 2 other-number-list)

答案 5 :(得分:8)

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

(reduce + some-numbers)

我立即知道您将序列转换为值。

使用apply我必须考虑应用了哪个功能:&#34;啊,它是+功能,所以我得到......一个数&#34 ;.稍微不那么直截了当。

答案 6 :(得分:6)

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

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

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) (vals 2))
我们可以说:

(apply some-fn vals)

并将其转换为等同于:

(some-fn val1 val2 val3)

所以,使用&#34; apply&#34;就像&#34;删除括号&#34;围绕着序列。

答案 7 :(得分:4)

关于这个话题有点迟了但我在阅读这个例子后做了一个简单的实验。这是我的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次调用。有人可以澄清这里真的发生了什么吗?

答案 8 :(得分:3)

apply的优点是给定函数(在这种情况下为+)可以应用于由具有结束集合的预先存在的干预参数形成的参数列表。 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)