foldrとfoldl(またはfoldl ')の関係


155

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

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

私のような賢い挑戦的なソートを助けることができるどんな洞察も大いに感謝されます!

回答:


174

以下のための再帰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


2
foldrではf y1 thunkと評価されるため、Falseを返しますが、foldlでは、fはそのパラメーターのどちらも認識できません。大きすぎる。foldl 'を実行するとすぐにサンクを減らすことができます。
Sawyer

25
混乱を避けるために、括弧は実際の評価順序を示していないことに注意してください。Haskellは怠惰なので、最も外側の式が最初に評価されます。
Lii 2013年

素晴らしい答え。リストの途中で停止できる折り畳みが必要な場合は、foldrを使用する必要があることを付け加えておきます。誤解しない限り、左折りは止められません。(あなたが「知っているなら...あなたは...リスト全体を行き来する」と言うとき、あなたはこれをほのめかします。)また、「値にのみ使用する」というタイプミスは「値にのみ使用する」に変更する必要があります。つまり、「オン」という単語を削除します。(Stackoverflowでは2文字の変更を送信できません!)。
Lqueryvg 2014年

@Lqueryvg左折を停止する2つの方法:1.右折でコーディングします(を参照fodlWhile)。2.それを左のスキャン(scanl)に変換し、last . takeWhile pまたは同様のもので停止します。ええと、3。を使用しますmapAccumL。:)
Will Ness

1
@Destyはfoldl、各ステップで全体的な結果の新しい部分を生成します。これは、全体的な結果を収集し、すべての作業が完了して実行するステップがなくなった後にのみ生成するためとは異なります。したがって、たとえばfoldl (flip (:)) [] [1..3] == [3,2,1]scanl (flip(:)) [] [1..] = [[],[1],[2,1],[3,2,1],...]... IOW、foldl f z xs = last (scanl f z xs)および無限リストには最後の要素がありません(上記の例では、それ自体がINFから1までの無限リストになります)。
Will Ness


33

その意味はあなただけ入れ替えることができないように異なるfoldlfoldr。1つは左から要素を、もう1つは右から折ります。このようにして、オペレーターは異なる順序で適用されます。これは、減算などのすべての非関連操作で重要です。

Haskell.orgにはこの件に関する興味深い記事があります。


それらのセマンティクスは些細な点でのみ異なるため、実際には意味がありません。使用される関数の引数の順序。したがって、インターフェースに関しては、それらは依然として交換可能と見なされます。本当の違いは、それは最適化/実装のみであるようです。
Evi1M4chine

2
@ Evi1M4chine違いは些細なものではありません。それどころか、それらは実質的です(そして、はい、実際には意味があります)。実際、もし私がこの答えを今日書けば、この違いをさらに強調するでしょう。
Konrad Rudolph

23

簡単に言うfoldrと、アキュムレータ関数が2番目の引数で遅延している場合に適しています。Haskell wikiのStack Overflowで詳細を読んでください(しゃれが意図されています)。


18

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

関数を取得しますsum = foldl['] (+) 0。ときにfoldl'使用され、合計はすぐにそう適用し、計算されたsumあなたのようなものを使用している場合は、無限のリストには、ちょうど(一定の空間内で最も可能性の高い永久に実行し、かつますIntsの、Doubleよ、Floatよ。Integersがあれば、一定のスペースよりも多く使用されます数がより大きいmaxBound :: Int)。

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

お役に立てば幸いです。


1
大きな例外は、渡された関数がfoldlコンストラクターを1つ以上の引数に適用するだけの場合です。
dfeuer 2016年

いつfoldl実際に最良の選択になるかについての一般的なパターンはありますか?(foldr間違った選択である場合、無限リストのように、最適化に関して。)
Evi1M4chine 2018年

@ Evi1M4chineはfoldr、無限リストの間違った選択であることの意味がわかりません。実際には、foldlまたはfoldl'無限リストには使用しないでください。スタックオーバーフローのHaskell wikiを
KevinOrr

14

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


2
このコメントは役に立ちますが、ソースをいただければ幸いです。
チタニウム

Common Lisp reduceは怠惰ではないためfoldl'、ここでの考慮事項の多くは適用されません。
MicroVirus

私はあなたの意味だと思うfoldl'彼らは厳格な言語ですようがありませんか、?そうでなければ、それはそれらのすべてのバージョンがそうするようにスタックオーバーフローを引き起こすことを意味foldlしないのですか?
Evi1M4chine

6

コンラートは指摘し、その意味は異なります。それらは同じタイプではありません:

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

タイプエラーが発生します。


それらのタイプは同じであり、単に入れ替えられただけであり、これは無関係です。厄介なP / NP問題を除いて、インターフェースと結果は同じです(読み取り:無限リスト)。;)私が知る限り、実装による最適化は、実際の唯一の違いです。
Evi1M4chine

@ Evi1M4chineこれは不正解です。次の例を見てください。foldl subtract 0 [1, 2, 3, 4]評価は-10foldr subtract 0 [1, 2, 3, 4]評価は-2です。foldl実際にある0 - 1 - 2 - 3 - 4一方です。foldr4 - 3 - 2 - 1 - 0
krapht 2018年

@ krapht、foldr (-) 0 [1, 2, 3, 4]is -2foldl (-) 0 [1, 2, 3, 4]is -10です。一方、subtract(何を期待するかもしれないから逆方向でsubtract 10 14ある4ので、)foldr subtract 0 [1, 2, 3, 4]である-10foldl subtract 0 [1, 2, 3, 4]される2(正)。
pianoJames
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.