web-dev-qa-db-ja.com

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は少し速いようです。

122
dbyrne

reduceapplyは、可変アリティの場合にすべての引数を見る必要がある連想関数に対して(返される最終結果に関して)当然同等です。結果的に同等である場合、applyは常に完全に慣用的であり、reduceは同等であり、瞬きのほんの一部を大幅に削減する可能性があります一般的なケースの。以下は、これを信じる理由です。

+自体は、可変アリティの場合(3つ以上の引数)のreduceの観点から実装されます。確かに、これは、任意の可変アライティブ連想関数を使用するための非常に賢明な「デフォルト」方法のように見えます。reduceは、おそらくinternal-reduce 、最近マスターで無効になった1.2のノベルティですが、将来的に再導入されることを期待しています-可変引数の場合にそれらから恩恵を受ける可能性のあるすべての関数で複製するのは愚かなことです。このような一般的なケースでは、applyは少しオーバーヘッドを追加します。 (実際に心配する必要はありません。)

一方、複雑な関数は、reduceに組み込まれるほど一般的ではない最適化の機会を利用する場合があります。 applyを使用するとそれらを利用できますが、reduceを使用すると実際に速度が低下する可能性があります。実際に発生する後者のシナリオの良い例は、strによって提供されます。内部でStringBuilderを使用し、applyではなくreduceを使用することで大きなメリットが得られます。

したがって、疑わしい場合はapplyを使用すると言います。そして、reduceで何も買わないことを知った場合(そして、これがすぐに変わることはまずありません)、reduceを使用して、必要であれば、それ。

118
Michał Marczyk

この答えを見ている初心者のために、
注意してください、それらは同じではありません:

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

意見はさまざまです-大規模なLISPの世界では、reduceはより慣用的であると考えられています。最初に、すでに議論された可変的な問題があります。また、いくつかのCommon LISPコンパイラは、applyが非常に長いリストに対して適用されると、引数リストの処理方法が原因で実際​​に失敗します。

私のサークルのClojuristsの間では、しかし、この場合applyを使用することはより一般的です。私はそれをより簡単に理解し、それを好むことも発見しました。

20
drcode

+は、任意の数の引数に適用できる特別なケースであるため、この場合には違いはありません。 reduceは、固定数の引数(2)を期待する関数を、任意の長い引数リストに適用する方法です。

19
G__

私は通常、あらゆる種類のコレクションを操作するときにreduceを好むことに気付きます-それはうまく機能し、一般的にはかなり便利な機能です。

私が適用する主な理由は、パラメーターが異なる位置で異なることを意味する場合、または初期パラメーターがいくつかありますが、コレクションから残りを取得したい場合です.

(apply + 1 2 other-number-list)
9
mikera

この特定のケースでは、reduceの方が好きです読みやすい

(reduce + some-numbers)

シーケンスを値に変換していることがすぐにわかります。

applyでは、どの関数が適用されているかを考慮する必要があります。「ああ、それは+関数なので、1つの数字を取得しています。」.

8
mascip

+のような単純な関数を使用する場合、どちらを使用するかは重要ではありません。

一般に、reduceは累積操作であるという考えです。現在の累積値と1つの新しい値を累積関数に提示します。関数の結果は、次の反復の累積値です。したがって、反復は次のようになります。

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

と言えます:

(apply some-fn vals)

そして、それは以下と同等に変換されます:

(some-fn val1 val2 val3)

したがって、「適用」を使用することは、シーケンスを囲む「括弧を削除する」ことに似ています。

6
Alan Thompson

トピックについて少し遅れましたが、この例を読んだ後に簡単な実験をしました。ここに、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の実装には何も見つかりませんでした。 applyのClojure実装は、replによってキャッシュされるreduceを内部的に呼び出します。これは、4番目の呼び出しを説明しているようです。ここで実際に何が起こっているのかを誰かが明確にできますか?

4
rohit

Applyの美しさは、関数(この場合は+)が与えられ、終了コレクションを使用して事前に保留中の引数によって形成される引数リストに適用できることです。 Reduceは、それぞれに関数を適用するコレクションアイテムを処理する抽象概念であり、可変引数の場合には機能しません。

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