Clojure:减少与应用

Clojure:减少与应用,clojure,Clojure,我理解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是一种将需

我理解
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—它执行得很好,通常是一个非常有用的函数

我使用apply的主要原因是,如果参数在不同位置表示不同的内容,或者如果您有几个初始参数,但希望从集合中获取其余参数,例如

(apply + 1 2 other-number-list)

reduce
apply
当然对于需要在变量arity情况下查看其所有参数的关联函数来说是等效的(就返回的最终结果而言)。当它们在结果方面是等价的时,我会说,
apply
总是非常地道,而
reduce
在许多常见情况下是等价的,可能会减少一小部分眨眼的时间。下面是我相信这一点的理由

+
本身是根据变量arity情况(超过2个参数)的
reduce
实现的。事实上,这似乎是一种非常合理的“默认”方法,可以用于任何变量的算术、关联函数:
reduce
有可能执行一些优化以加快速度——可能是通过类似于
内部reduce
的方式,这是master中最近禁用的1.2新功能,但希望将来能重新引入——在vararg案例中,在每个可能受益于它们的函数中进行复制是愚蠢的。在这种常见情况下,
apply
只会增加一点开销。(请注意,这没什么好担心的。)

另一方面,一个复杂的函数可能会利用一些优化机会,这些机会不够普遍,无法内置到
reduce
;然后,
apply
会让你利用这些优势,而
reduce
实际上可能会让你慢下来。
str
提供了后一种情况在实践中发生的一个很好的例子:它在内部使用
StringBuilder
,并将从使用
apply
而不是
reduce
中获得显著的好处


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

在更大的Lisp世界中,观点不一,
reduce
显然被认为更惯用。首先,已经讨论了可变因素问题。此外,一些常见的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)
我马上就知道你在把一个序列变成一个值


使用<代码>应用< /C> >我必须考虑应用哪个函数:“啊,它是<代码> +/COD>函数,所以我得到…一个单一的数字”。稍微简单一点。

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

一般来说,
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)

因此,使用“apply”就像是在序列周围“去掉括号”。

这个主题有点晚了,但在阅读了这个示例之后,我做了一个简单的实验。这是我的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 reduce的源代码,它使用内部reduce实现了非常干净的递归,但在apply的实现上并没有发现任何东西。+的Clojure实现用于内部调用repl缓存的apply-invoke-reduce,这似乎解释了第四个调用。有人能解释一下这里到底发生了什么吗?

apply is give函数的美妙之处(+在本例中)可以应用于由预挂起的中间参数和结束集合组成的参数列表。Reduce是处理集合项的抽象,为每个集合项应用函数,不适用于变量args case

(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)

回答得很好。另一方面,为什么不像haskell那样包含一个内置的
sum
函数呢?看起来是个很普通的手术。谢谢,很高兴听到这个!Re:
sum
,我想说Clojure有这个函数,它叫做
+
,你可以将它与
apply
:-)一起使用严肃地说,我认为在Lisp中,一般来说,如果提供了一个可变函数,它通常不会附带一个对集合进行操作的包装器——这就是您使用的
apply
(或
reduce
),如果您知道这样做更有意义的话