Clojure:削減と適用


126

私は概念的な違いを理解する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)

しかし、どちらがより慣用的なクロジュールですか?どちらにしても大きな違いはありますか?私の(制限された)パフォーマンステストから、それreduceは少し速いようです。

回答:


125

reduceそしてapplyもちろんのみ同等である可変アリティの場合、すべての引数を参照する必要が連想機能のための(最終的な結果の観点では、返されました)。それらが結果的に同等である場合、私はそれapplyが常に完全に慣用的である一方reduceで、同等であり、多くの一般的なケースで瞬きのほんの一部を削り取る可能性があると言います。以下はこれを信じる私の理論的根拠です。

+reduce可変アリティの場合(2つ以上の引数)に関してそれ自体が実装されています。確かに、これは可変アリアの連想関数reduceを実行するための非常に賢明な「デフォルト」の方法のように見えます。物事をスピードアップするためにいくつかの最適化を実行する可能性internal-reduceがあります。うまくいけば、将来的に再導入される予定です-可変引数の場合にそれらから利益を得る可能性のあるすべての関数で複製することは愚かなことです。このような一般的なケースでapplyは、わずかなオーバーヘッドが追加されます。(実際に心配する必要はありません。)

一方、複雑な関数は、に組み込むには一般的ではないいくつかの最適化の機会を利用する場合がありreduceます。その後、実際にあなたを遅くするかもしれないapply間、あなたはそれらを利用することができますreduce。後者のシナリオが実際に発生する良い例を以下に示しますstr。これはStringBuilder内部で使用するため、applyではなくを使用することで大きなメリットを得られますreduce

だから、私applyは疑わしいときに使用すると言います。そして、それがあなたに何も買わないことを知っている場合reduce(そして、これがすぐに変わる可能性は低いことを知っている場合)、reduce必要に応じて、その小さな不要なオーバーヘッドを取り除いてください。


すばらしい答えです。sum余談ですが、haskellのような組み込み関数を含めてみませんか?かなり一般的な操作のようです。
dbyrne 2010

17
よろしくお願いします。Re sum:、私はClojureにこの関数がある+と思いますapply。それは呼び出され、で使用できます。:-)真剣に言えば、Lispでは、一般に、可変個の関数が提供されている場合、通常、コレクションを操作するラッパーを伴わないと思います-これは、使用するものですapply(またはreduce、それがより理にかなっている場合)。
のMichałMarczyk

6
おかしい、私のアドバイスは反対です。reduce疑わしいときはapply、最適化があると確信しているとき。reduceの契約はより正確であり、一般的な最適化が行われやすくなっています。applyよりあいまいなので、ケースバイケースでのみ最適化できます。strconcat2つの一般的な例外です。
cgrand 2013

1
@cgrand私の根拠の言い換えは、機能のためにすることをおおよそであるかもしれないreduceし、apply結果の面で同等であり、私は彼らの可変長の過負荷を最適化する最善の方法を知っているだけの用語の中でそれを実装するために、当該機能の作者を期待するreduce場合それが実際に最も意味のあることです(そうするためのオプションは確かに常に利用可能であり、非常に実用的なデフォルトになります)。ただし、あなたがどこから来たのかreduceは確かにわかりますが、Clojureのパフォーマンスストーリーの中心であり(ますますそうなっています)、非常に高度に最適化され、非常に明確に指定されています。
のMichałMarczyk

51

この答えを見ている初心者の
場合、注意してください、彼らは同じではありません:

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

21

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

ただし、私のサークルのClojuristsの間ではapply、この場合に使用する方が一般的です。私はそれを手探りするのも簡単で、それを好みます。


19

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


9

私は通常、あらゆる種類のコレクションに作用するときは、reduceを好むことに気づきます。

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

(apply + 1 2 other-number-list)

9

この特定のケースでは、reduceより読みやすいので、私が好みます。

(reduce + some-numbers)

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

apply私が適用される機能を検討する必要があります:「ああ、それはだ+、私は...、単一の番号を取得していますので、機能」。ややわかりにくい。


7

+のような単純な関数を使用する場合、どの関数を使用してもかまいません。

一般的には、これ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)

したがって、「適用」の使用は、シーケンスの周りの「括弧の削除」のようなものです。


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実装は、replによってキャッシュされるreduceを内部的に呼び出します。これは、4番目の呼び出しを説明しているようです。誰かが本当にここで何が起こっているのかを明確にできますか?


私はできる限り削減することを好みます:)
rohit

2
フォームrange内に呼び出しを入れるべきではありませんtime。シーケンス構築の干渉を取り除くためにそれを外側に置きます。私の場合、reduce一貫してパフォーマンスが優れていapplyます。
Davyzhu 2015年

3

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.