リストを処理し、追加の値(通常は関数が生成された時点では存在しません)を受け取った後、その要素を使用して何かを実行する関数を構築する関数型言語の例をたくさん見てきました。
-
(「遅延評価」の最後の2つの例)
リストのステージングは、ML / OCamlなどの厳密な関数型言語で追加し、最初のリストを複数回トラバースしないようにします。
(「ステージング」というタイトルのセクション)
foldrを使用してリストを別のリストと比較する(つまり、別のリストを最初のリストと比較する関数を生成する)
listEq a b = foldr comb null a b where comb x frec [] = False comb x frec (e:es) = x == e && frec es cmp1To10 = listEq [1..10]
これらすべての例で、著者は通常、元のリストを1回だけトラバースすることの利点を指摘しています。しかし、「確かに、N個の要素のリストをトラバースする代わりに、N個の評価のチェーンをトラバースしているので、何をするのか」と考えずにはいられません。私はそれに何らかの利益があるに違いないことを知っています、誰かがそれを説明してもらえますか?
編集:答えてくれた両方に感謝します。残念ながら、それは私が知りたかったことではありません。私は自分の質問を明確にしようとしますので、中間リストの作成に関する(より一般的な)質問(私はすでにさまざまな場所で読んだもの)と混同しないでください。また、投稿のフォーマットを修正していただきありがとうございます。
リストに適用する関数を作成する場合に興味があります。この場合、結果を評価するために必要な値がまだありません(リストであるかどうかは関係ありません)。次に、各リスト要素への参照の生成を回避することはできません(リスト構造が参照されなくなった場合でも)。また、以前と同じメモリアクセスがありますが、リストを分解する必要はありません(パターンマッチング)。
たとえば、前述のMLブックの「ステージング」の章を参照してください。私はMLとRacketで試してみました。具体的には、最初のリストを何度もトラバースせずに、最初のリストをトラバースし、2番目のリストを末尾に挿入する関数を返す「append」のステージバージョンです。驚いたことに、最後のポインターがそれぞれの場合で異なっていたため、リスト構造をコピーする必要があることを考慮しても、はるかに高速でした。
以下はマップの変形であり、リストに適用した後、関数を変更するときに高速になるはずです。listMap [1..100000]
Haskellは厳密ではないので、 inの評価を強制する必要がありますcachedList
(または、最初のアプリケーションの後もまだメモリ内にあるはずなので、そうではないかもしれません)。
listMap = foldr comb (const [])
where comb x rest = \f -> f x : rest f
cachedList = listMap [1..100000]
doubles = cachedList (2*)
squares = cachedList (\x -> x*x)
-- print doubles and squares
-- ...
comb x rest f = ...
Haskellではvsを使用しても違いがないことは知っていますが(間違っている場合は訂正してください)comb x rest = \f -> ...
、このバージョンを選択してアイデアを強調しました。
更新:いくつかの簡単なテストの後、Haskellでの実行時間に違いは見つかりませんでした。問題は、Scheme(少なくとも私がテストしたRacket実装)やMLなどの厳密な言語についてのみです。