ええと、少し深く見てみると、どちらも実際には基本言語の配列も含んでいます。
- 5番目の改訂されたスキームレポート(R5RS)には、ランダムタイプの線形時間よりも優れた固定サイズの整数インデックスコレクションであるベクトルタイプが含まれています。
- Haskell 98レポートにも配列タイプがあります。
ただし、関数型プログラミング命令では、長い間、配列または二重リンクリストよりも単一リンクリストが強調されてきました。実際、かなり強調されすぎている可能性があります。ただし、これにはいくつかの理由があります。
1つ目は、単一リンクリストが最も単純でありながら最も有用な再帰データ型の1つであることです。Haskellのリスト型に相当するユーザー定義は、次のように定義できます。
data List a -- A list with element type `a`...
= Empty -- is either the empty list...
| Cell a (List a) -- or a pair with an `a` and the rest of the list.
リストが再帰的なデータ型であることは、リストで機能する関数が一般に構造的な再帰を使用することを意味します。Haskellの用語では、リストコンストラクターでパターンマッチを行い、リストのサブパートで再帰します。これら2つの基本的な関数定義では、変数を使用しas
てリストの末尾を参照しています。したがって、再帰呼び出しはリストを「下る」ことに注意してください。
map :: (a -> b) -> List a -> List b
map f Empty = Empty
map f (Cell a as) = Cell (f a) (map f as)
filter :: (a -> Bool) -> List a -> List a
filter p Empty = Empty
filter p (Cell a as)
| p a = Cell a (filter p as)
| otherwise = filter p as
この手法は、関数がすべての有限リストに対して確実に終了することを保証します。また、問題を解決する優れた手法でもあります。問題を自然に、より単純でより扱いやすいサブパートに分割する傾向があります。
したがって、単一リンクリストはおそらく、関数型プログラミングで非常に重要なこれらの手法を学生に紹介するのに最適なデータ型です。
2番目の理由は、「なぜ単一リンクリストなのか」という理由ではなく、「なぜ二重リンクリストや配列でないのか」という理由です。後者のデータ型では、関数型プログラミングが頻繁に行われる変異(変更可能な変数)を必要とします。遠ざかる。それが起こるように:
- Schemeのような熱心な言語では、ミューテーションを使用しないと二重リンクリストを作成できません。
- Haskellのような遅延言語では、ミューテーションを使用せずに二重リンクリストを作成できます。しかし、そのリストに基づいて新しいリストを作成するときは常に、元の構造のすべてではないにしても、ほとんどをコピーする必要があります。一方、単一リンクリストでは、「構造共有」を使用する関数を記述できます。新しいリストは、必要に応じて古いリストのセルを再利用できます。
- 従来、不変の方法で配列を使用した場合、配列を変更するたびにすべてをコピーする必要がありました。(
vector
ただし、最近のHaskellライブラリでは、この問題を大幅に改善する手法が見つかりました)。
3番目の最後の理由は、主にHaskellなどの遅延言語に適用されます。実際には、遅延単一リンクリストは、適切なメモリ内リストよりも反復子に似ていることがよくあります。コードがリストの要素を順番に消費していき、それらを破棄する場合、オブジェクトコードは、リストを進めていくときにリストのセルとその内容のみを実体化します。
これは、リスト全体が一度にメモリに存在する必要はなく、現在のセルだけが存在する必要があることを意味します。現在のセルより前のセルはガベージコレクションされます(二重リンクリストでは不可能です)。現在のセルより後のセルは、そこに到達するまで計算する必要はありません。
それだけではありません。いくつかの一般的なHaskellライブラリで使用されている技術は、fusionと呼ばれます。コンパイラは、リスト処理コードを分析し、順次生成および消費されて「捨てられる」中間リストを見つけます。この知識があれば、コンパイラーはそれらのリストのセルのメモリー割り当てを完全に排除できます。つまり、Haskellソースプログラムの単一リンクリストは、コンパイル後、実際にはデータ構造ではなくループになる可能性があります。
Fusionは、前述のvector
ライブラリが不変配列の効率的なコードを生成するために使用する手法でもあります。同じことが非常に人気のあるbytestring
(バイト配列)およびtext
(Unicode文字列)ライブラリにも当てはまります。これらのライブラリは、Haskellのそれほど大きくないネイティブString
型([Char]
文字の単一リンクリストと同じ)の代わりに作成されました。したがって、現代のHaskellでは、フュージョンをサポートする不変の配列型が非常に一般的になる傾向があります。
リスト融合は、単一リンクリストにあなたが行くことができるという事実によって促進される前方が、決して後ろ向き。これは、関数型プログラミングにおいて非常に重要なテーマをもたらします。データ型の「形状」を使用して計算の「形状」を導出することです。要素を順次処理する場合、単一リンクリストはデータ型であり、構造的な再帰でそれを使用すると、そのアクセスパターンを非常に自然に提供します。「分割統治」戦略を使用して問題を攻撃する場合、ツリーデータ構造はそれを非常によくサポートする傾向があります。
多くの人々は早い段階で関数型プログラミングワゴンから脱落するため、単一リンクリストに触れることはできますが、より高度な基本的なアイデアに触れることはありません。