Mendalami dua properti yang membedakan Haskell dari semua bahasa mainstream: purity yang membuat fungsi deterministik dan mudah diparalelkan, serta lazy evaluation yang memungkinkan infinite list dengan take dan cycle, termasuk kapan harus memaksa evaluasi dengan operator $! dan bang patterns untuk menghindari space leak.

Setelah di episode 6 kalian menguasai list dan fungsi higher-order, saatnya memahami apa yang membuat Haskell berbeda secara fundamental dari Python, Java, atau Go: purity dan laziness. Kedua properti ini bukan sekadar "fitur menarik" — keduanya mengubah cara program berperilaku, cara menulisnya, dan cara men-debug-nya.
Mengapa bab ini penting? Karena kesalahpahaman soal laziness adalah sumber paling umum space leak di aplikasi produksi, dan pemahaman purity adalah pintu menuju konteks monad di episode 11. Dua konsep ini juga yang menjelaskan mengapa Haskeller begitu percaya diri terhadap kode mereka: fungsi pure adalah fungsi yang bisa dibuktikan.
Fungsi pure memiliki dua sifat yang bisa diuji:
tambahDiskon :: Double -> Double -> Double
tambahDiskon harga diskon = harga * (1 - diskon)tambahDiskon 100 0.1 selalu menghasilkan 90.0, tidak peduli kapan, di mana, atau berapa kali dipanggil. Sebaliknya, fungsi yang membaca file, memakai waktu saat ini, atau mengubah variabel global adalah fungsi impure — hasilnya tergantung konteks.
Karena pure, fungsi Haskell punya properti yang luar biasa:
tambahDiskon 100 0.1 cukup.Program nyata butuh I/O — membaca file, mencetak, query database. Haskell tidak melarangnya; ia mengisolasi-nya lewat tipe IO. Signatur IO adalah pengumuman: "fungsi ini berinteraksi dengan dunia luar". Aturan mainnya:
IO = kode pure, bebas efek samping.IO = efek samping terjadi di sana, dan hanya di sana.pureHitung :: Double -> Double -- aman, murni
pureHitung x = x * 2
impureCetak :: IO () -- efek samping diisolasi
impureCetak = putStrLn "halo"Garis pemisah yang eksplisit ini — yang di bahasa lain tidak ada — membuat kalian selalu tahu di mana "hal-hal berbahaya" terjadi. Kita bedah IO dan monad di episode 11.
Haskell lazy: ekspresi tidak dievaluasi sampai hasilnya benar-benar dibutuhkan. Ini lawan dari eager evaluation (Python, Java, C) yang menghitung semuanya langsung.
hampirInfinite :: [Int]
hampirInfinite = [1..][1..] adalah list tak hingga. Di bahasa eager, ekspresi ini langsung hang (mencoba membuat list tanpa akhir). Di Haskell, [1..] hanyalah thunk — janji nilai yang belum dihitung. Nilai sebenarnya baru dihitung saat elemen diminta:
sepuluhPertama :: [Int]
sepuluhPertama = take 10 [1..] -- [1,2,3,4,5,6,7,8,9,10]take 10 meminta 10 elemen pertama — dan hanya 10 elemen itu yang dihitung. Inilah yang membuat ekspresi seperti [1..] menjadi sah di Haskell: komputasi ditangguhkan, dan program tidak melakukan pekerjaan yang tidak diminta.
cycle mengulang list selamanya, dan dengan laziness pengulangan itu tidak pernah benar-benar dibangun:
pola :: [String]
pola = take 6 (cycle ["on", "off"]) -- ["on","off","on","off","on","off"]Kombinasi cycle + take adalah idiom umum untuk membuat urutan periodik.
Setiap ekspresi yang belum dievaluasi disimpan sebagai thunk — struktur data yang "mengingat" cara menghitungnya. Konsekuensi praktisnya:
Contoh klasik space leak:
sumBesar :: [Int] -> Int
sumBesar = foldl (+) 0Pada list besar, foldl membangun rantai thunk yang sangat dalam baru dievaluasi di akhir. Perbaikannya adalah memaksa evaluasi tiap langkah:
import Data.List (foldl')
sumBesarStrict :: [Int] -> Int
sumBesarStrict = foldl' (+) 0foldl' mengevaluasi akumulator setiap langkah, sehingga memori tetap konstan. Episode 21 akan membedah masalah ini dan solusi lanjutannya secara mendalam.
Kadang kalian tahu pasti sebuah nilai akan dibutuhkan — memaksanya dievaluasi lebih awal bisa menghemat memori dan mencegah thunk menumpuk. Operator $! memaksa evaluasi argumen kanannya:
import Data.List (foldl')
sumSatu :: [Int] -> Int
sumSatu xs = foldl' (+) 0 xsDi sisi lain, bang patterns (dengan ekstensi BangPatterns) memaksa evaluasi saat binding dibuat:
{-# LANGUAGE BangPatterns #-}
-- !x berarti x wajib dievaluasi sebelum fungsi memproses
f :: Int -> Int -> Int
f !x !y = x + yAturan emas: jangan menambah strictness tanpa alasan. Laziness adalah perilaku default yang sudah benar; $! dan bang patterns adalah alat bedah yang hanya dipakai saat profiling menunjukkan thunk menumpuk (dibahas di episode 21).
Warning
Kebocoran memori di Haskell jarang berasal dari "memegang data terlalu lama" — penyebabnya hampir selalu thunk yang menumpuk karena nilai tidak pernah dievaluasi. Jika program terlihat semakin lambat dan boros memori seiring waktu, cek pola foldl vs foldl' dan akumulasi hasil parsial yang menunda evaluasi.
Kombinasi keduanya saling menguatkan. Karena fungsi pure (deterministik), compiler bebas menunda evaluasi tanpa mengubah hasil. Karena evaluasi lazy, struktur data tak hingga bisa "berpura-pura" ada dan dikonsumsi sebagian. Desain ini menghasilkan kode yang ditulis deklaratif (apa yang diinginkan) sementara mesin evaluasi mengurus kapan menghitungnya.
foldl (bukan foldl') pada list besar — space leak klasik.show atau length pada list tak hingga — length [1..] akan hang, karena ia butuh seluruh list.$! di mana-mana "biar cepat" — biasanya malah melambat. Ukur dulu dengan profiler.Inti yang harus dibawa pulang:
IO; kode tanpa IO dijamin murni.[1..] dan cycle legal, hanya bagian yang dipakai yang dihitung.foldl', $!, dan bang patterns.Di episode 8 selanjutnya kita akan membahas typeclasses & ad-hoc polymorphism — dari Eq, Ord, Show, Read, Num sampai mendefinisikan instance sendiri, lalu menaiki tangga Functor, Applicative, dan Monad yang menjadi dasar komposisi efek. Sampai jumpa di episode 8!