Belajar Haskell - Lists, Tuples & Pattern Matching
Episode 6 of 23

Belajar Haskell - Lists, Tuples & Pattern Matching

Mendalami struktur data paling penting di Haskell: list homogen [a] dengan list comprehension, fungsi higher-order map/filter/foldl/foldr, tuple sebagai nilai komposit yang bisa menyimpan tipe berbeda, serta pattern matching untuk mendekonstruksi list dengan (x:xs), tuple dan wildcard _ termasuk perulangan dengan rekursi.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 5 kalian memahami tipe dasar dan type inference, saatnya mempelajari struktur data yang paling sering dipakai di seluruh ekosistem Haskell: list dan tuple. Hampir semua komputasi data — parsing, transformasi, pipeline, sampai query database — berputar di sekitar list dan fungsi-fungsi yang bekerja padanya.

Mengapa bab ini penting? Karena list adalah cara Haskell menggantikan perulangan for/while. Alih-alih "loop sambil mengubah array", kalian mentransformasi list dengan map, menyaring dengan filter, dan mereduksi dengan fold. Setelah episode ini, kalian akan membaca kode Haskell seperti pipeline data — dan pola inilah yang dipakai di episode 14 untuk streaming.

List: Struktur Data Homogen

List di Haskell ditulis dalam kurung siku dan harus berisi tipe yang sama:

List dasar
angka :: [Int]
angka = [1, 2, 3, 4, 5]
 
kata :: [String]
kata = ["apel", "jeruk", "mangga"]

List bisa dibangun dengan operator kons : (cons) dan diakhiri list kosong []:

Membangun list dengan kons
[1, 2, 3] == 1 : 2 : 3 : []

1 : 2 : 3 : [] adalah bentuk kanonik list: kons bersifat right-associative, jadi [1,2,3] adalah 1 : (2 : (3 : [])). Ini penting karena pattern matching pada list justru bekerja pada struktur ini.

Range dan List Comprehension

Range menghasilkan list berurutan secara elegan:

Range
limaPertama :: [Int]
limaPertama = [1..5]      -- [1,2,3,4,5]
 
genap :: [Int]
genap = [2,4..10]         -- [2,4,6,8,10]

List comprehension adalah sintaks berbasis set-builder notation untuk membangun list dari list lain — persis seperti notasi matematika:

List comprehension
kuadratGenap :: [Int]
kuadratGenap = [x * x | x <- [1..10], x `mod` 2 == 0]

Dibaca: "untuk setiap x yang diambil dari [1..10], jika x mod 2 sama dengan 0, hasilkan x * x". Hasilnya [4,16,36,64,100]. Kalian juga bisa mengkombinasikan beberapa generator untuk menghasilkan produk kartesian.

Higher-Order Functions: map, filter, fold

Fungsi yang menerima fungsi lain sebagai argumen disebut higher-order function. Tiga yang paling fundamental:

map

map - terapkan fungsi ke tiap elemen
doubleAll :: [Int] -> [Int]
doubleAll xs = map (*2) xs

map f xs menerapkan f ke setiap elemen dan mengembalikan list hasil. Jenis fungsi total: input list apa pun selalu menghasilkan output valid.

filter

filter - pertahankan elemen yang memenuhi predikat
onlyEven :: [Int] -> [Int]
onlyEven xs = filter even xs

filter p xs mempertahankan elemen yang membuat predikat p bernilai True.

foldl dan foldr

fold mereduksi list menjadi satu nilai. foldl mengakumulasi dari kiri, foldr dari kanan:

foldl dan foldr
jumlah :: [Int] -> Int
jumlah xs = foldl (+) 0 xs
 
concatAll :: [[Int]] -> [Int]
concatAll xss = foldr (++) [] xss

foldl (+) 0 [1,2,3] adalah ((0 + 1) + 2) + 3. foldr adalah fondasi komposisi list yang paling fleksibel — di Haskell, map dan filter bisa ditulis ulang sebagai foldr. Perbedaan halus keduanya (soal laziness dan space leak) akan kita bahas di episode 21.

Note

Pola yang paling sering kalian lihat di kode produksi Haskell adalah rantai pipeline: filter lalu map lalu fold. Contoh nyatanya akan muncul di episode 14 (streaming data) dan episode 15 (query database) — kuasai ketiga fungsi ini dan kalian sudah menguasai 80% gaya menulis Haskell.

Tuple: Mengelompokkan Nilai Berbeda

Berbeda dari list yang homogen, tuple mengelompokkan nilai dengan tipe berbeda dalam satu nilai komposit:

Tuple dengan tipe berbeda
user :: (String, Int)
user = ("devan", 29)
 
koordinat :: (Double, Double)
koordinat = (-6.2, 106.8)

Tuple punya ukuran tetap dan tiap posisi punya tipe tersendiri: ("devan", 29) bertipe (String, Int), bukan (Int, String) — urutan dan tipe menentukan tipe tuple. Akses elemen pertama lewat fst dan kedua lewat snd:

Akses elemen tuple
nama :: String
nama = fst user   -- "devan"

Untuk tuple lebih dari dua elemen, pattern matching adalah cara yang jauh lebih nyaman.

Pattern Matching: Mendekonstruksi Data

Pattern matching membalik proses konstruksi: memecah data sesuai bentuk asalnya. Untuk list, pola paling penting adalah (x:xs) — "elemen pertama x, sisa xs":

Pola (x:xs) - elemen pertama dan sisa
headOf :: [Int] -> Int
headOf (x:xs) = x
 
tailOf :: [Int] -> [Int]
tailOf (x:xs) = xs

Dengan (x:xs), kalian bisa menulis rekursi — pengganti for loop di Haskell:

Rekursi dengan (x:xs)
sumList :: [Int] -> Int
sumList []     = 0
sumList (x:xs) = x + sumList xs

sumList [] = 0 adalah base case; sumList (x:xs) = x + sumList xs memecah list, menjumlahkan elemen pertama, lalu berlanjut ke sisa. Inilah cara berpikir perulangan di Haskell: base case + langkah rekursif.

Pattern pada Tuple dan Wildcard

Tuple dan wildcard _ ikut serta dalam pola:

Pattern tuple dan wildcard
describeUser :: (String, Int) -> String
describeUser (nama, umur) = nama ++ " berumur " ++ show umur
 
namaPertama :: (String, Int) -> String
namaPertama (nama, _) = nama

(nama, _) memecah tuple dan mengabaikan elemen kedua. Wildcard _ dipakai setiap kali kita tidak peduli dengan nilai di posisi itu — misalnya sebagai fallback pada pola yang tidak lengkap.

Guards + Pattern: Kombinasi Kuat

Pattern dan guards sering dipakai bersamaan:

Pattern + guards
sign :: Int -> String
sign x
    | x > 0     = "positif"
    | x < 0     = "negatif"
    | otherwise = "nol"

Untuk list yang lebih spesifik, kalian bisa mengkombinasikan beberapa pola:

Multi-pattern pada list
describe :: [Int] -> String
describe []       = "kosong"
describe [x]      = "satu elemen: " ++ show x
describe (x:y:_)  = "minimal dua elemen"

Baris ketiga (x:y:_) hanya mencocokkan list dengan minimal dua elemen — _ menangkap sisa yang tidak kita pedulikan.

Kesalahan Umum (Common Pitfalls)

  1. Pattern tidak lengkap: headOf [] crash dengan "non-exhaustive patterns". Selalu sediakan kasus [].
  2. List heterogen: [1, "satu"] tidak terkompilasi — list harus homogen; gunakan tuple atau ADT (episode 8) untuk mencampur tipe.
  3. foldl pada list tak hingga — dengan evaluasi lazy, foldl bisa hang. Praktik baik modern: foldl' (strict) dari Data.List. Kita bahas di episode 21.
  4. Urutan pola: Haskell mencoba dari atas ke bawah. Letakkan pola paling spesifik lebih dulu, _ paling akhir.

Penutup

Inti yang harus dibawa pulang:

  • List adalah struktur data homogen [a], dibangun dengan : dan [].
  • List comprehension [ekspr | x <- xs, kondisi] membangun list secara deskriptif.
  • map (transformasi), filter (penyaringan), foldl/foldr (reduksi) menggantikan perulangan imperatif.
  • Tuple (a, b) mengelompokkan tipe berbeda; akses dengan fst/snd atau pattern matching.
  • Pattern matching dengan (x:xs) mendekonstruksi list; kombinasikan dengan guards dan wildcard _.
  • Selalu sediakan base case dan hindari pattern tidak lengkap.

Di episode 7 selanjutnya kita akan membahas purity, laziness & evaluasi — mengapa fungsi pure membuat kode mudah diuji dan diparalelkan, bagaimana evaluasi lazy memungkinkan infinite list [1..] dengan take/cycle, serta kapan harus memaksa evaluasi dengan $! dan bang patterns untuk performa. Sampai jumpa di episode 7!

Belajar Haskell - Lists, Tuples & Pattern Matching | Belajar Haskell