私は 2 つの Haskell 関数を持っていますが、どちらも私と非常に似ているようです。しかし、最初のものは無限リストに対して失敗し、2 つ目は無限リストに対して成功します。その理由を正確に突き止めようと何時間も試みてきましたが、役に立ちませんでした。
どちらのスニペットも、Prelude の「単語」機能を再実装したものです。どちらも有限リストに対してうまく機能します。
無限リストを処理しないバージョンは次のとおりです。
myWords_FailsOnInfiniteList :: String -> [String]
myWords_FailsOnInfiniteList string = foldr step [] (dropWhile charIsSpace string)
where
step space ([]:xs) | charIsSpace space = []:xs
step space (x:xs) | charIsSpace space = []:x:xs
step space [] | charIsSpace space = []
step char (x:xs) = (char : x) : xs
step char [] = [[char]]
無限リストを処理するバージョンは次のとおりです。
myWords_anotherReader :: String -> [String]
myWords_anotherReader xs = foldr step [""] xs
where
step x result | not . charIsSpace $ x = [x:(head result)]++tail result
| otherwise = []:result
注: 「charIsSpace」は、単に Char.isSpace の名前を変更したものです。
次のインタープリター セッションは、最初のセッションが無限リストに対して失敗し、2 番目のセッションが成功することを示しています。
*Main> take 5 (myWords_FailsOnInfiniteList (cycle "why "))
*** Exception: stack overflow
*Main> take 5 (myWords_anotherReader (cycle "why "))
["why","why","why","why","why"]
編集:以下の回答のおかげで、私は今理解していると思います。これが私の結論と修正されたコードです。
結論:
- 私の最初の試みの最大の原因は、「step space []」と「step char []」で始まる 2 つの方程式でした。ステップ関数の 2 番目のパラメーターを [] に対して一致させることは、2 番目の引数全体を強制的に評価するため、no-noです (ただし、以下で説明する注意事項があります)。
- ある時点で、(++) は右辺の引数を cons よりも遅く評価するのではないかと考えていました。そこで、「= (char:x):xs」を「= [char : x] ++ xs」に変更することで問題を解決できるのではないかと考えました。しかし、それは間違っていました。
- ある時点で、2 番目の引数を (x:xs) に対してパターン マッチングすると、関数が無限リストに対して失敗するのではないかと考えました。私はこれについてほぼ正しかったが、完全ではない! 上記のパターン マッチで行ったように、(x: xs ) に対して 2 番目の引数を評価すると、何らかの再帰が発生します。「:」(別名、「短所」)に達するまで「クランクを回します」。それが起こらなければ、私の関数は無限リストに対して成功しません。ただし、この特定のケースでは、私の関数は最終的にスペースに遭遇し、その時点で「短所」が発生するため、すべて問題ありません。また、(x:xs) に対するマッチングによってトリガーされる評価は、その場で停止し、無限再帰を回避します。その際、「x」はマッチしますが、xsはサンクのままなので問題ありません。(それを理解するのを本当に助けてくれたGaneshに感謝します)。
- 一般に、評価を強制しない限り、2 番目の引数について自由に言及できます。x:xs と照合した場合は、xs の評価を強制しない限り、必要なだけ xs に言及できます。
というわけで、修正後のコードです。私は通常、head と tail を避けようとしますが、それは単にそれらが部分的な関数であるという理由と、同等のパターン マッチングを書く練習が必要だからです。
myWords :: String -> [String]
myWords string = foldr step [""] (dropWhile charIsSpace string)
where
step space acc | charIsSpace space = "":acc
step char (x:xs) = (char:x):xs
step _ [] = error "this should be impossible"
これは、無限リストに対して正しく機能します。先頭、末尾、または (++) 演算子が見えないことに注意してください。
さて、重要な注意点として、 最初に修正したコードを書いたとき、「step _ []」に一致する 3 番目の式がありませんでした。その結果、網羅的でないパターン マッチに関する警告を受け取りました。明らかに、その警告を回避することをお勧めします。
しかし、私は問題が発生するだろうと思っていました。2 番目の引数を [] に対してパターン マッチすることは問題ないことは既に述べました。しかし、警告を取り除くためにそうしなければなりません。
しかし、「step_[]」式を追加すると、すべてうまくいきました!無限リストでも問題ありませんでした!. なんで?
修正されたコードの 3 番目の式は決して到達しないためです。
実際、次の BROKEN バージョンを考えてみましょう。空のリストのパターンを他のパターンの上に移動したことを除いて、正しいコードとまったく同じです。
myWords_brokenAgain :: String -> [String]
myWords_brokenAgain string = foldr step [""] (dropWhile charIsSpace string)
where
step _ [] = error "this should be impossible"
step space acc | charIsSpace space = "":acc
step char (x:xs) = (char:x):xs
step が呼び出されたときに最初に発生することは、インタープリターが式 1 が一致するかどうかを確認することであるため、スタック オーバーフローに戻ります。そのためには、2 番目の引数が [] であるかどうかを確認する必要があります。そのためには、2 番目の引数を評価する必要があります。
方程式を他の方程式の下に移動すると、最初または 2 番目のパターンのいずれかが常に一致するため、3 番目の方程式が試行されないことが保証されます。3 番目の式は、網羅的ではないパターン警告を省略するためのものです。
これは素晴らしい学習経験でした。ご協力いただきありがとうございます。