どのようにfold
sが異なることは混乱の頻繁な源であると思われるので、ここではより一般的な概要は次のとおりです。
[x1, x2, x3, x4 ... xn ]
関数f
とシードを使用して、n個の値のリストを折りたたむことを検討してくださいz
。
foldl
です:
- 左結合:
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- 末尾再帰:リストを反復処理し、後で値を生成します
- Lazy:結果が必要になるまで何も評価されません
- 逆方向:
foldl (flip (:)) []
リストを逆にします。
foldr
です:
- 右連想:
f x1 (f x2 (f x3 (f x4 ... (f xn z) ... )))
- 引数への再帰:各反復は
f
、次の値と、リストの残りを折りたたんだ結果に適用されます。
- Lazy:結果が必要になるまで何も評価されません
- Forwards:
foldr (:) []
リストを変更せずに返します。
ここには、時々人々をつまずかせる少し微妙なポイントがあります。なぜなら、foldl
は逆であるので、の各アプリケーションは結果の外側にf
追加されます。そしてlazyなので、結果が必要になるまで何も評価されません。つまり、結果の一部を計算するために、Haskellは最初にリスト全体を反復処理して、ネストされた関数アプリケーションの式を作成し、次に最も外側の関数を評価し、必要に応じてその引数を評価します。常に最初の引数を使用する場合、これはHaskellが最も内側の項まで再帰し、次にの各アプリケーションを逆方向に計算する必要があることを意味します。f
f
これは明らかに、ほとんどの関数型プログラマーが知っており、愛している効率的な末尾再帰とはかけ離れています。
実際、foldl
技術的に末尾再帰ですが、結果式全体が何かを評価する前に構築されるfoldl
ため、スタックオーバーフローが発生する可能性があります。
一方、考慮してくださいfoldr
。また、怠け者だが、それが実行されるため、前方に、それぞれのアプリケーションがf
に追加された内部結果の。したがって、結果を計算するために、Haskellは単一の関数アプリケーションを構築します。その2番目の引数は、折りたたまれたリストの残りの部分です。f
がその2番目の引数(データコンストラクターなど)で遅延している場合、結果は段階的に遅延され、フォールドの各ステップは、それを必要とする結果の一部が評価されたときにのみ計算されます。
そうしないと、foldr
ときどき無限リストが機能する理由がわかりますfoldl
。前者は無限リストを別の遅延無限データ構造に遅延変換できますが、後者はリスト全体を検査して結果の一部を生成する必要があります。一方、のfoldr
ように両方の引数をすぐに必要とする関数では、のよう(+)
に機能する(または機能しない)のでfoldl
、評価する前に巨大な式を作成します。
したがって、注意すべき2つの重要な点は次のとおりです。
foldr
ある遅延再帰データ構造を別の構造に変換できます。
- そうしないと、大規模または無限のリストでスタックオーバーフローが発生して、レイジーフォールドがクラッシュします。
あなたはそれfoldr
がすべてfoldl
ができることに加えてもっとできるように聞こえることに気づいたかもしれません。これは本当です!実際、foldlはほとんど役に立ちません!
しかし、大きな(ただし、無限ではない)リストを折りたたむことにより、遅延のない結果を生成したい場合はどうでしょうか。このために、我々はしたい厳格倍、標準ライブラリはthoughfully提供を:
foldl'
です:
- 左結合:
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- 末尾再帰:リストを反復処理し、後で値を生成します
- Strict:各関数アプリケーションは途中で評価されます
- 逆方向:
foldl' (flip (:)) []
リストを逆にします。
のでfoldl'
あり、厳密な、Haskellはなり結果を計算するために評価する f
代わりに、左の引数をさせるのは、各ステップでは、巨大な、未評価の表現を蓄積します。これにより、通常の効率的な末尾再帰が実現します。言い換えると:
foldl'
大きなリストを効率的に折りたたむことができます。
foldl'
無限リストで無限ループ(スタックオーバーフローを引き起こさない)でハングします。
Haskell wikiには、これについて説明するページもあります。
foldr
よりも優れているfoldl
ではHaskellの反対がで真の間、アーラン(私は前に学んだハスケル)。以来、アーランは怠惰ではなく、機能がされていないカレーので、foldl
でErlangのような挙動foldl'
上。これは素晴らしい答えです!お疲れ様でした!