Nah, jika Anda melihat sedikit lebih dalam, keduanya sebenarnya termasuk array dalam bahasa dasar juga:
- Laporan Skema ke-5 yang direvisi (R5RS) mencakup jenis vektor , yang merupakan kumpulan indeks-bilangan bulat ukuran tetap dengan lebih baik daripada waktu linier untuk akses acak.
- The Haskell 98 Report juga memiliki tipe array .
Instruksi pemrograman fungsional, bagaimanapun, telah lama menekankan daftar single-linked over array atau double-linked list. Bahkan agaknya terlalu ditekankan. Namun, ada beberapa alasan untuk itu.
Yang pertama adalah bahwa daftar yang ditautkan tunggal adalah salah satu tipe data rekursif paling sederhana namun paling berguna. Setara yang ditentukan pengguna dari jenis daftar Haskell dapat didefinisikan seperti ini:
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.
Fakta bahwa daftar adalah tipe data rekursif berarti bahwa fungsi yang bekerja pada daftar umumnya menggunakan rekursi struktural . Dalam istilah Haskell: Anda mencocokkan pola pada konstruktor daftar, dan Anda mengulang pada sub bagian dari daftar. Dalam dua definisi fungsi dasar ini, saya menggunakan variabel as
untuk merujuk ke ujung daftar. Jadi perhatikan bahwa panggilan rekursif "turun" ke bawah daftar:
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
Teknik ini menjamin bahwa fungsi Anda akan berakhir untuk semua daftar yang terbatas, dan juga merupakan teknik pemecahan masalah yang baik — ia cenderung membagi masalah secara alami menjadi sub-bagian yang lebih sederhana dan lebih dapat dipertahankan.
Jadi daftar yang terhubung tunggal mungkin adalah tipe data terbaik untuk memperkenalkan siswa pada teknik ini, yang sangat penting dalam pemrograman fungsional.
Alasan kedua adalah kurang dari alasan "mengapa daftar tunggal-link", tetapi lebih dari alasan "mengapa tidak daftar-ganda atau array" mengapa: tipe data yang terakhir sering panggilan untuk mutasi (variabel yang dapat dimodifikasi), yang pemrograman fungsional sangat sering menjauh dari. Jadi seperti yang terjadi:
- Dalam bahasa yang bersemangat seperti Skema, Anda tidak dapat membuat daftar ditautkan ganda tanpa menggunakan mutasi.
- Dalam bahasa malas seperti Haskell, Anda dapat membuat daftar tautan ganda tanpa menggunakan mutasi. Tetapi setiap kali Anda membuat daftar baru berdasarkan yang itu, Anda dipaksa untuk menyalin sebagian besar jika tidak semua struktur aslinya. Sedangkan dengan daftar yang ditautkan tunggal, Anda dapat menulis fungsi yang menggunakan "berbagi struktur" —daftar baru dapat menggunakan kembali sel daftar lama jika diperlukan.
- Secara tradisional, jika Anda menggunakan array dengan cara yang tidak berubah itu berarti setiap kali Anda ingin memodifikasi array Anda harus menyalin semuanya. (Perpustakaan Haskell terbaru seperti
vector
, bagaimanapun, telah menemukan teknik yang sangat memperbaiki masalah ini).
Alasan ketiga dan terakhir berlaku untuk bahasa-bahasa malas seperti Haskell terutama: daftar-daftar tunggal-malas, dalam praktiknya, sering lebih mirip dengan iterator daripada daftar dalam-memori yang tepat. Jika kode Anda menggunakan elemen daftar secara berurutan dan membuangnya saat Anda pergi, kode objek hanya akan mematerialisasi sel daftar dan kontennya saat Anda melangkah maju melalui daftar.
Ini berarti bahwa seluruh daftar tidak perlu ada dalam memori sekaligus, hanya sel saat ini. Sel sebelum yang sekarang dapat berupa sampah yang dikumpulkan (yang tidak mungkin dilakukan dengan daftar yang ditautkan ganda); sel lebih lambat dari yang sekarang tidak perlu dihitung sampai Anda tiba di sana.
Bahkan lebih jauh dari itu. Ada teknik yang digunakan di beberapa perpustakaan Haskell populer, yang disebut fusion , di mana kompiler menganalisis kode pemrosesan daftar Anda dan melihat daftar perantara yang sedang dihasilkan dan dikonsumsi secara berurutan dan kemudian "dibuang." Dengan pengetahuan ini maka kompiler dapat sepenuhnya menghilangkan alokasi memori sel daftar tersebut. Ini berarti bahwa daftar yang ditautkan tunggal dalam program sumber Haskell, setelah dikompilasi, mungkin benar-benar berubah menjadi loop alih-alih struktur data.
Fusion juga merupakan teknik yang digunakan oleh vector
perpustakaan tersebut untuk menghasilkan kode yang efisien untuk array yang tidak berubah. Sama berlaku untuk sangat populer bytestring
(byte array) dan text
(Unicode string) perpustakaan, yang dibangun sebagai pengganti asli tidak-sangat-besar Haskell String
jenis (yang sama dengan [Char]
, daftar tunggal-linked karakter). Jadi di Haskell modern ada tren di mana tipe array yang tidak berubah dengan dukungan fusi menjadi sangat umum.
Daftar fusi difasilitasi oleh fakta bahwa dalam daftar yang terhubung tunggal Anda dapat maju tetapi tidak pernah mundur . Ini memunculkan tema yang sangat penting dalam pemrograman fungsional: menggunakan "bentuk" dari tipe data untuk menurunkan "bentuk" dari suatu perhitungan. Jika Anda ingin memproses elemen secara berurutan, daftar yang ditautkan tunggal adalah tipe data yang, ketika Anda mengkonsumsinya dengan rekursi struktural, memberi Anda pola akses tersebut dengan sangat alami. Jika Anda ingin menggunakan strategi "membagi dan menaklukkan" untuk menyerang masalah, maka struktur data pohon cenderung mendukungnya dengan sangat baik.
Banyak orang keluar dari gerobak pemrograman fungsional sejak awal, sehingga mereka mendapatkan eksposur ke daftar-tautan tunggal tetapi tidak dengan ide-ide mendasar yang lebih maju.