まず、Real World Haskell(これは私が読んでいます)では、foldl
を使用せず、代わりにfoldl'
。だから私はそれを信頼しています。
しかし、foldr
対foldl'
。私は目の前でそれらがどのように異なって機能するかの構造を見ることができますが、「どちらが良いか」を理解するには愚かすぎます。どちらを使用するかは問題ではないように思えますが、どちらも同じ答えを生成します(そうではありませんか?)。実際、このコンストラクトに関する私の以前の経験は、Rubyのinject
とClojureのreduce
からのもので、「左」バージョンと「右」バージョンがないようです。 (側面の質問:どのバージョンを使用していますか?)
私のような賢明な挑戦に役立つ洞察は大歓迎です!
foldr f x ys
の再帰ys = [y1,y2,...,yk]
は次のようになります
f y1 (f y2 (... (f yk x) ...))
一方、foldl f x ys
の再帰は次のようになります
f (... (f (f x y1) y2) ...) yk
ここで重要な違いは、f x y
の結果がx
の値のみを使用して計算できる場合、foldr
はリスト全体を調べる必要がないことです。例えば
foldr (&&) False (repeat False)
False
を返しますが、
foldl (&&) False (repeat False)
終了しません。 (注:repeat False
は、すべての要素がFalse
である無限リストを作成します。)
一方、foldl'
は末尾再帰で厳密です。何に関係なくリスト全体をトラバースする必要があることがわかっている場合(たとえば、リスト内の数値を合計する場合)、foldl'
はfoldr
よりもスペース(およびおそらく時間)効率的です。 。
それらのセマンティクスは異なるため、foldl
とfoldr
を交換することはできません。 1つは要素を左から上に折り、もう1つは右から折ります。そのようにして、演算子は異なる順序で適用されます。これは、減算などのすべての非連想演算にとって重要です。
Haskell.org には興味深い article があります。
まもなく、アキュムレータ関数が2番目の引数で遅延している場合、foldr
の方が優れています。 Haskell wikiの Stack Overflow (punpun)で詳細を読んでください。
すべての使用の99%で_foldl'
_がfoldl
よりも好ましい理由は、ほとんどの使用で一定のスペースで実行できるためです。
関数sum = foldl['] (+) 0
を使用します。 _foldl'
_を使用すると、合計がすぐに計算されるため、sum
を無限リストに適用すると、永久に実行され、ほとんどの場合は一定のスペースで実行されます(Int
s、Double
s、Float
s。Integer
sは、数値が_maxBound :: Int
_)より大きくなると、一定以上のスペースを使用します。
foldl
を使用すると、サンクが構築されます(回答を保存するのではなく、後で評価できる回答を取得する方法のレシピのように)。これらのサンクは多くのスペースを占有する可能性があります。この場合、サンクを格納するよりも式を評価する方がはるかに優れています(スタックオーバーフローにつながります。
お役に立てば幸いです。
ところで、Rubyのinject
とClojureのreduce
はfoldl
(またはfoldl1
、使用するバージョンに応じて)。通常、言語にフォームが1つしかない場合は、Pythonのreduce
、PerlのList::Util::reduce
、C++のaccumulate
、C#のAggregate
、Smalltalkのinject:into:
、PHPのarray_reduce
、MathematicaのFold
など。CommonLISPのreduce
はデフォルトで左折りになりますが、右折りのオプションがあります。
Konrad が指摘するように、それらのセマンティクスは異なります。彼らは同じタイプさえ持っていません:
ghci> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
ghci>
たとえば、リスト追加演算子(++)は、次のようにfoldr
を使用して実装できます。
(++) = flip (foldr (:))
ながら
(++) = flip (foldl (:))
型エラーが発生します。