web-dev-qa-db-ja.com

foldr対foldl(またはfoldl ')の意味

まず、Real World Haskell(これは私が読んでいます)では、foldlを使用せず、代わりにfoldl'。だから私はそれを信頼しています。

しかし、foldrfoldl'。私は目の前でそれらがどのように異なって機能するかの構造を見ることができますが、「どちらが良いか」を理解するには愚かすぎます。どちらを使用するかは問題ではないように思えますが、どちらも同じ答えを生成します(そうではありませんか?)。実際、このコンストラクトに関する私の以前の経験は、RubyのinjectとClojureのreduceからのもので、「左」バージョンと「右」バージョンがないようです。 (側面の質問:どのバージョンを使用していますか?)

私のような賢明な挑戦に役立つ洞察は大歓迎です!

146
J Cooper

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よりもスペース(およびおそらく時間)効率的です。 。

165
Chris Conway

foldrは次のようになります。

Right-fold visualization

foldlは次のようになります。

Left-fold visualization

コンテキスト: Fold Haskell wikiで

52
Greg Bacon

それらのセマンティクスは異なるため、foldlfoldrを交換することはできません。 1つは要素を左から上に折り、もう1つは右から折ります。そのようにして、演算子は異なる順序で適用されます。これは、減算などのすべての非連想演算にとって重要です。

Haskell.org には興味深い article があります。

33
Konrad Rudolph

まもなく、アキュムレータ関数が2番目の引数で遅延している場合、foldrの方が優れています。 Haskell wikiの Stack Overflow (punpun)で詳細を読んでください。

23
mattiast

すべての使用の99%で_foldl'_がfoldlよりも好ましい理由は、ほとんどの使用で一定のスペースで実行できるためです。

関数sum = foldl['] (+) 0を使用します。 _foldl'_を使用すると、合計がすぐに計算されるため、sumを無限リストに適用すると、永久に実行され、ほとんどの場合は一定のスペースで実行されます(Ints、Doubles、Floats。Integersは、数値が_maxBound :: Int_)より大きくなると、一定以上のスペースを使用します。

foldlを使用すると、サンクが構築されます(回答を保存するのではなく、後で評価できる回答を取得する方法のレシピのように)。これらのサンクは多くのスペースを占有する可能性があります。この場合、サンクを格納するよりも式を評価する方がはるかに優れています(スタックオーバーフローにつながります。

お役に立てば幸いです。

18
Axman6

ところで、RubyのinjectとClojureのreducefoldl(またはfoldl1、使用するバージョンに応じて)。通常、言語にフォームが1つしかない場合は、Pythonのreduce、PerlのList::Util::reduce、C++のaccumulate、C#のAggregate、Smalltalkのinject:into:、PHPのarray_reduce、MathematicaのFoldなど。CommonLISPのreduceはデフォルトで左折りになりますが、右折りのオプションがあります。

14
newacct

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

型エラーが発生します。

6
Jonas