ほとんどの関数型言語は、最も基本的なリストタイプとして、単方向リンクリスト(「cons」リスト)を使用していることに気づきました。例としては、Common LISP、Haskell、F#などがあります。これは、ネイティブリストタイプが配列である主流の言語とは異なります。
何故ですか?
Common LISP(動的に型付けされている)の場合、コンスはリストやツリーなどのベースにもなるのに十分一般的であるという考えが得られます。これは小さな理由かもしれません。
ただし、静的型付け言語の場合、適切な理由を見つけることができず、反対の引数を見つけることもできます。
では、なぜリンクリストを好むのでしょうか。
最も重要な要素は、O(1)時間で不変の単一リンクリストに先頭に追加できることです。これにより、O(n)このような時間:
// Build a list containing the numbers 1 to n:
foo(0) = []
foo(n) = cons(n, foo(n-1))
不変の配列を使用してこれを実行した場合、各cons
操作で配列全体をコピーする必要があるため、実行時間は2次となり、ランタイムは2次になります。
機能的なスタイルは不変性を促進し、データの共有も促進します。配列はリンクされたリストよりも「部分的に」共有する方が簡単です
「部分的に」共有するとは、O(1)時間で配列からサブ配列を取得できることを意味しますが、リンクリストではO(1)時間とその他すべてにはO(n)が必要です。
しかし、多くの場合、尾を取るだけで十分です。また、安価に配列を作成する方法がない場合、安価にサブ配列を作成できることは役に立たないことを考慮する必要があります。そして(賢いコンパイラ最適化なしでは)安価に配列を段階的に構築する方法はありません。
私は、リストが関数コードでかなり簡単に実装されていることが原因だと思います。
スキーム:
(define (cons x y)(lambda (m) (m x y)))
Haskell:
data [a] = [] | a : [a]
配列はより難しく、実装するのはそれほどきれいではありません。それらを非常に高速にしたい場合は、低レベルで記述する必要があります。
さらに、再帰は配列よりもリストではるかにうまく機能します。リストを再帰的に消費/生成した回数と、配列にインデックスを付けた回数を検討してください。
単一リンクリストは、最も単純な 永続的なデータ構造 です。
永続的なデータ構造は、高性能で純粋に関数型のプログラミングに不可欠です。
ガベージコレクションされた言語がある場合にのみ、Consノードを簡単に使用できます。
短所ノードは、再帰呼び出しや不変値の関数型プログラミングスタイルとよく一致します。したがって、メンタルプログラマモデルによく適合します。
そして、歴史的な理由を忘れないでください。なぜ彼らはまだコンスノードと呼ばれ、さらに悪いことにアクセサーとして車とCDRを使用していますか?人々は教科書やコースからそれを学び、それを使います。
そうです、現実の世界では、アレイの方がはるかに使いやすく、メモリスペースの半分しか消費せず、キャッシュレベルのミスによりパフォーマンスが向上します。命令型言語でそれらを使用する理由はありません。
リンクリストは次の理由で重要です。
3のような数値を取得し、それをsucc(succ(succ(zero)))
のような後続シーケンスに変換し、次に{succ=List node with some memory space}
と{zero = end of list}
を使用して置換すると、最終的にはリンクされたリスト(長さ3)。
実際の重要な部分は、数値、置換、およびメモリ空間とゼロです。