この質問 のmyAny関数のコードは、foldrを使用します。述語が満たされると、無限リストの処理を停止します。
Foldlを使用して書き直しました。
myAny :: (a -> Bool) -> [a] -> Bool
myAny p list = foldl step False list
where
step acc item = p item || acc
(ステップ関数の引数は正しく逆になっていることに注意してください。)
ただし、無限リストの処理は停止しなくなりました。
Apocalisp's answer のように関数の実行をトレースしようとしました。
myAny even [1..]
foldl step False [1..]
step (foldl step False [2..]) 1
even 1 || (foldl step False [2..])
False || (foldl step False [2..])
foldl step False [2..]
step (foldl step False [3..]) 2
even 2 || (foldl step False [3..])
True || (foldl step False [3..])
True
ただし、これは関数の動作方法ではありません。これはどうですか?
fold
sがどのように異なるかは、混乱の原因となることが多いため、より一般的な概要を次に示します。
N個の値のリスト[x1, x2, x3, x4 ... xn ]
を関数f
とシードz
で折り畳むことを検討してください。
foldl
は:f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
foldl (flip (:)) []
はリストを逆にします。foldr
は:f x1 (f x2 (f x3 (f x4 ... (f xn z) ... )))
f
を次の値とリストの残りの折り畳みの結果に適用します。foldr (:) []
はリストを変更せずに返します。ここには時々人をつまずかせる微妙なポイントがあります:foldl
はbackwardsであるため、f
の各アプリケーションはoutsideに追加されます結果;また、lazyであるため、結果が必要になるまで何も評価されません。つまり、結果の任意の部分を計算するために、Haskellは最初にentire listを反復処理して、ネストされた関数アプリケーションの式を構築し、次にoutermost関数、必要に応じて引数を評価します。 f
が常に最初の引数を使用する場合、これはHaskellが最も内側の用語まで再帰し、f
の各アプリケーションを逆方向に計算する必要があることを意味します。
これは、ほとんどの機能プログラマーが知っており、愛している効率的な末尾再帰とは明らかにかけ離れています!
実際、foldl
は技術的に末尾再帰ですが、結果式全体が何かを評価する前に構築されるため、foldl
はスタックオーバーフローを引き起こす可能性があります!
一方、foldr
を検討してください。また、怠、ですが、forwardsを実行するため、f
の各アプリケーションはinsideに追加されます結果。そのため、結果を計算するために、Haskellはsingle関数アプリケーションを構築します。その2番目の引数は折り畳まれたリストの残りの部分です。 f
の2番目の引数(データコンストラクターなど)が遅延している場合、結果はincrementally lazyになり、フォールドの各ステップが計算されます結果の必要な部分が評価される場合のみ。
したがって、foldr
が機能しない場合にfoldl
が時々無限リストで機能する理由を見ることができます:前者は無限リストを別の遅延データ構造に遅延変換できますが、後者はリスト全体を検査して結果の一部を生成する必要があります。一方、(+)
など、すぐに両方の引数を必要とする関数を持つfoldr
は、foldl
のように機能します(または機能しません)。評価する前に巨大な式を作成します。
そのため、注意すべき2つの重要なポイントは次のとおりです。
foldr
は、ある遅延再帰データ構造を別の遅延データ構造に変換できます。foldr
は、foldl
ができることのすべてに加えて、さらに多くのことができるように聞こえます。これは本当です!実際、foldlはほとんど役に立ちません!
しかし、大きな(ただし無限ではない)リストを折り返すことで遅延のない結果を生成する場合はどうでしょうか。このために、strict foldが必要です。これは 標準ライブラリが提供する :
foldl'
は:f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
foldl' (flip (:)) []
はリストを逆にします。foldl'
はstrictであるため、結果を計算するためにHaskellはevaluate各ステップでletする代わりにf
左の引数は、巨大で評価されていない式を蓄積します。これにより、通常の効率的な末尾再帰が得られます。言い換えると:
foldl'
は、大きなリストを効率的に折りたたむことができます。foldl'
は、無限リストで無限ループ(スタックオーバーフローを引き起こさない)でハングします。Haskell wikiには これについて議論しているページ もあります。
myAny even [1..]
foldl step False [1..]
foldl step (step False 1) [2..]
foldl step (step (step False 1) 2) [3..]
foldl step (step (step (step False 1) 2) 3) [4..]
等.
直感的に、foldl
は常に「外側」または「左側」にあるため、最初に展開されます。広告無限。
Haskellのドキュメントで見ることができます here foldlは末尾再帰であり、無限のリストが渡されると終了しません。値を返す前に次のパラメーターで自身を呼び出すためです...
Haskellを知らないが、Schemeではfold-right
は常にリストの最後の要素を最初に「実行」します。したがって、isは循環リストでは機能しません(無限リストと同じです)。
fold-right
は末尾再帰で記述できますが、循環リストの場合はスタックオーバーフローが発生します。 fold-left
OTOHは通常、末尾再帰で実装されており、早期に終了しない場合、無限ループに陥ります。