List, tuple, dan range

Struktur data inti Haskell yang digunakan sejak hari pertama: list homogen dan tuple.

Daftar isi

List dan tuple adalah dua struktur data yang digunakan hampir setiap program Haskell sejak hari pertama. List menyimpan banyak nilai bertipe sama dengan panjang yang bebas, sedangkan tuple menyimpan sejumlah nilai tetap yang tipe komponennya boleh berbeda. Topik ini membahas penulisan keduanya, notasi range untuk menghasilkan list berurutan, serta pertimbangan memilih salah satunya.

List#

List adalah urutan nilai yang seluruh elemennya bertipe sama. Notasi [1, 2, 3] adalah bentuk ringkas dari penyambungan elemen satu per satu dengan operator :, yang menyisipkan satu elemen di depan list. Nilai 1 : 2 : 3 : [] sama persis dengan [1, 2, 3], dan list kosong ditulis []. Karena panjangnya bebas, fungsi yang memproses list selalu menangani dua kasus, yaitu list kosong dan pasangan kepala-ekor.

haskell operasi-list.hs
-- List homogen: seluruh elemennya bertipe sama
belanja :: [String]
belanja = ["beras", "telur", "minyak"]

angka :: [Int]
angka = 1 : 2 : 3 : []  -- sama dengan [1, 2, 3]

main :: IO ()
main = do
  print (head belanja)
  print (tail angka)
  print (angka ++ [4, 5])
  print (length belanja)
text Keluaran
"beras"
[2,3]
[1,2,3,4,5]
3

Beberapa fungsi standar yang paling sering digunakan terlihat pada program di atas. Fungsi head mengambil elemen pertama, tail mengambil sisa setelah elemen pertama, operator ++ menyambung dua list, dan length menghitung banyak elemen. Sifat homogen list tetap dijaga oleh semua fungsi tersebut, sehingga [1, 2] tidak dapat disambung dengan ["dua"] karena tipenya berbeda dan compiler menolaknya.

Tidak satu pun operasi di atas mengubah list aslinya, melainkan menghasilkan list baru, sehingga angka tetap bernilai [1, 2, 3] setelah angka ++ [4, 5] dihitung. Sifat ini konsisten dengan data immutable Haskell dan membuat alur data mudah dilacak. Anotasi tipe ikut membantu pembacaan, sebab [String] dibaca list dari String, dan [Int] dibaca list dari Int.

Range#

Range menghasilkan list berurutan berdasarkan pola naik atau turunnya. Bentuk [a .. b] menyusun nilai dari a hingga b dengan langkah satu, sedangkan bentuk [a, b .. c] menerima dua elemen awal untuk menentukan langkahnya. Notasi ini bekerja untuk tipe mana pun yang anggotanya dapat disusun berurutan, seperti Int dan Char.

haskell range-list.hs
-- Range menghasilkan list dari batas dan pola langkahnya
genap :: [Int]
genap = [2, 4 .. 10]

huruf :: [Char]
huruf = ['a' .. 'e']

main :: IO ()
main = do
  print genap
  print huruf
  print (sum [1 .. 100])
  print (take 4 [10, 20 ..])
text Keluaran
[2,4,6,8,10]
"abcde"
5050
[10,20,30,40]

Range juga dapat dibuka di satu sisi, misalnya [10, 20 ..] yang menaik tanpa batas. List semacam itu hanya aman pada bahasa dengan evaluasi lazy, dan penggunaan seperti take 4 pada program di atas mengambil bagian yang diperlukan saja. Kasus khusus yang mudah terlewat adalah batas menurun yang ditulis dengan sintaks naik, sebagaimana perbandingan berikut.

bash
# batas menurun dengan langkah naik menghasilkan list kosong
ghc -e '[5 .. 1]'
# langkah menurun dinyatakan lewat dua elemen awal
ghc -e '[5, 4 .. 1]'
text Keluaran
[]
[5,4,3,2,1]

Ekspresi [5 .. 1] menghasilkan list kosong karena langkahnya tetap naik, sedangkan [5, 4 .. 1] menerima langkah turun dari pasangan elemen awalnya. Perilaku ini ditetapkan oleh spesifikasi bahasa dan bukan keputusan versi tertentu, sehingga rentang yang hasilnya meleset perlu diperiksa dari arah langkahnya lebih dahulu.

Kedua tipe pada program di atas menunjukkan luas jangkauan notasi ini. Karena Int dan Char sama-sama memiliki urutan, range menghasilkan list bilangan dan list huruf dengan penulisan yang persis sama. Batas awal dan akhirnya juga wajib bertipe sama, sehingga campuran seperti [1 .. 'z'] langsung ditolak compiler sebagai perbandingan antar tipe yang berbeda.

Tuple#

Tuple menggabungkan sejumlah nilai dengan tipe yang boleh berbeda, dan panjangnya tetap sejak diciptakan. Pasangan (10, "sepuluh") menyatukan Int dan String menjadi satu nilai bertipe (Int, String), sesuatu yang tidak mungkin pada list. Akses komponennya juga berbeda karena tidak ada indeks angka, melainkan pola atau fungsi khusus pasangan seperti fst dan snd.

haskell tuple-waktu.hs
-- Tuple menampung beberapa nilai dengan tipe yang boleh berbeda
durasiMenit :: (Int, Int) -> Int
durasiMenit (jam, menit) = jam * 60 + menit

pengejaWaktu :: (Int, Int) -> String
pengejaWaktu (jam, menit) = show jam ++ " jam " ++ show menit ++ " menit"

main :: IO ()
main = do
  print (durasiMenit (2, 30))
  print (pengejaWaktu (2, 30))
  print (fst (10, "sepuluh"))
  print (snd (10, "sepuluh"))
text Keluaran
150
"2 jam 30 menit"
10
"sepuluh"

Fungsi durasiMenit dan pengejaWaktu menerima pasangan jam dan menit, lalu membongkarnya lewat pola pada argumen. Dua baris terakhir memperlihatkan fst dan snd yang mengambil komponen pasangan tanpa pola, dan keduanya hanya tersedia untuk pasangan dua elemen. Tuple dengan tiga elemen atau lebih dibongkar melalui pola karena fungsi tersebut tidak menjangkau komponen ketiganya.

Tuple juga boleh berisi tuple, sehingga nilai seperti ((1, 2), 3) sah dan dibongkar dengan pola bersarang ((x, y), z). Panjang serta tipe setiap komponen menjadi bagian dari tipe tuplenya, sehingga (Int, Int) dan (Int, Int, Int) adalah dua tipe yang berbeda meskipun isinya mirip. Perbedaan tersebut membuat compiler mencegah penggunaan tuple dua elemen pada tempat yang menuntut tiga elemen.

Memilih list atau tuple#

Kedua struktur menyimpan beberapa nilai, tetapi melayani kebutuhan yang berbeda. List melayani koleksi yang panjangnya ditentukan saat program berjalan, sedangkan tuple melayani pengelompokan tetap yang komponennya sudah pasti sejak ditulis. Tabel berikut merangkum pertimbangannya.

AspekListTuple
Panjangbebas, dapat bertambah dan berkurangtetap sejak diciptakan
Tipe elemensemua elemen wajib samasetiap komponen boleh berbeda
Pola khas[] dan x : xs(x, y) dan seterusnya
Penggunaan lazimderetan data sejenis, misalnya nilai ujianpasangan koordinat atau jam-menit

Aturan praktisnya sederhana, yaitu gunakan list apabila banyaknya elemen tidak diketahui saat kode ditulis, dan gunakan tuple apabila kelompok nilainya sudah pasti. Deretan nilai ujian yang banyaknya berubah-ubah tepat disimpan sebagai list, sedangkan satu titik koordinat dengan pasangan tetap sebaiknya disimpan sebagai tuple. Keduanya sering bekerja bersama, misalnya list berisi tuple untuk mewakili deretan pasangan jam dan menit, dan pencocokan pola menjadikan pembongkaran keduanya sama mudahnya.

Apabila kebutuhan data tumbuh lebih kompleks, misalnya setiap komponen memerlukan nama yang bermakna, langkah lanjutannya adalah mendeklarasikan tipe data sendiri. List dan tuple tetap digunakan sebagai penyusun di dalam deklarasi tersebut, sehingga pemahaman keduanya tetap terpakai pada struktur yang lebih rumit.

Ringkasan#

  • List adalah urutan homogen yang panjangnya bebas, dengan 1 : 2 : 3 : [] sebagai bentuk penuh dari [1, 2, 3].
  • Range menghasilkan list berurutan dari batas dan langkahnya, termasuk list tak hingga yang diambil sebagian dengan take.
  • Tuple menggabungkan nilai bertipe bebas dalam panjang tetap, dengan fst dan snd untuk pasangan dua elemen.
  • List dipilih untuk koleksi yang panjangnya dinamis, sedangkan tuple dipilih untuk kelompok nilai yang komposisinya tetap.

Referensi#