Memahami typeclass sebagai mekanisme ad-hoc polymorphism di Haskell: kelas standar Eq Ord Show Read Num, mendefinisikan instance sendiri dengan deriving dan implementasi manual, lalu menaiki hierarki Functor Applicative Monad beserta operator <$> <*> dan >>= sebagai fondasi komposisi efek.

Setelah di episode 7 kalian memahami purity dan laziness, saatnya mempelajari mekanisme yang membuat Haskell fleksibel tanpa kehilangan keamanan tipe: typeclass. Di sinilah letak jawaban untuk "bagaimana satu operator == bisa bekerja untuk Int, String, dan tipe buatan saya?" — dan inilah juga fondasi dari Functor, Applicative, dan Monad yang akan menemani kita mulai episode 11.
Mengapa bab ini penting? Karena typeclass adalah bahasa kontrak di ekosistem Haskell. Setiap kali kalian membaca tipe seperti Eq a => ... atau Monad m => ..., kalian sedang membaca persyaratan: "tipe a harus mendukung operasi tertentu". Menguasai hierarki ini berarti kalian bisa membaca dan merancang API Haskell dengan benar.
Typeclass adalah kumpulan fungsi yang "dijanjikan" oleh sebuah tipe. Analogi paling dekat: interface di Java, trait di Rust, atau protocol di Python — dengan perbedaan bahwa typeclass bersifat ad-hoc: kalian bisa mendefinisikan instance untuk tipe milik orang lain tanpa mengubah tipe tersebut.
class Greeter a where
greet :: a -> Stringclass Greeter a where berarti "kelas Greeter berisi fungsi greet yang bekerja untuk tipe a". Siapa pun bisa menjadi anggota Greeter selama menyediakan greet.
| Typeclass | Kemampuan | Fungsi kunci |
|---|---|---|
Eq | Kesetaraan | ==, /= |
Ord | Perbandingan | <, >, <=, >=, compare |
Show | Konversi ke String | show |
Read | Parse dari String | read |
Num | Operasi numerik | +, -, *, negate, fromInteger |
Functor | Memetakan fungsi ke dalam konteks | fmap |
Applicative | Menerapkan fungsi di dalam konteks | pure, <*> |
Monad | Komposisi efek berurutan | >>=, return |
Perhatikan tanda kurung pada Eq: operator-operator ini dalam Haskell bukan built-in magic — mereka adalah fungsi typeclass biasa yang di-overload. Ini bedanya dengan bahasa lain di mana == diukir di dalam bahasa.
Note
Show adalah teman debugging terbaik kalian: show 42 menghasilkan "42", show True menghasilkan "True". Setiap kali GHC mengeluh "no instance for Show", biasanya karena kalian mencoba mencetak tipe yang belum punya representasi teks — tambahkan deriving Show dan masalah selesai.
Ada dua cara: deriving (otomatis) untuk typeclass sederhana, dan implementasi manual untuk yang kompleks.
data Warna = Merah | Hijau | Biru
deriving (Show, Eq, Ord)Sekali deriving, kalian bisa show Merah, Merah == Hijau, Merah < Biru — semuanya otomatis dan konsisten. Untuk tipe data aljabar sederhana, selalu mulai dari deriving.
Untuk tipe yang perilakunya tidak standar, implementasikan sendiri:
data Suhu = Suhu Double -- suhu dalam Celsius
instance Show Suhu where
show (Suhu c) = show c ++ "°C"
instance Eq Suhu where
Suhu a == Suhu b = a == binstance Show Suhu where ... mengisi fungsi show untuk tipe Suhu. Sekarang show (Suhu 25.0) mencetak "25.0°C".
Tiga typeclass ini adalah inti dari programming with effects. Idenya: banyak komputasi terjadi "di dalam konteks" — nilai yang bisa Nothing, list yang bisa banyak, aksi yang berinteraksi dengan dunia luar. Tangga ketiganya menyediakan cara memanipulasi nilai di dalam konteks tersebut.
class Functor f where
fmap :: (a -> b) -> f a -> f bfmap menerapkan fungsi ke nilai di dalam struktur f, tanpa mengubah strukturnya:
fmap (+1) (Just 5) -- Just 6
fmap (+1) Nothing -- Nothing
fmap (+1) [1,2,3] -- [2,3,4]Maybe a dan list keduanya adalah Functor. Pada Maybe, fmap "tidak menyentuh" Nothing; pada list, fmap adalah map.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f bpure memasukkan nilai biasa ke dalam konteks; <*> menerapkan fungsi yang sudah ada di dalam konteks:
pure (+) <*> Just 3 <*> Just 5 -- Just 8
(+) <$> Just 3 <*> Just 5 -- Just 8, memakai fmapNotasi <$> adalah infix dari fmap — kombinasi <$> dan <*> adalah idiom standar untuk "menerapkan fungsi multi-argumen pada nilai-nilai dalam konteks". Di sini Just 8 berarti: karena kedua input valid, hasilnya valid.
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m bOperator >>= (dibaca "bind") mengalirkan nilai di dalam konteks ke fungsi berikutnya yang mengembalikan konteks baru — memungkinkan komposisi berurutan di mana langkah berikutnya bisa bergantung pada hasil langkah sebelumnya:
saatJelas :: Int -> Maybe Int
saatJelas n
| n > 0 = Just (n * 2)
| otherwise = Nothing
Just 5 >>= saatJelas -- Just 10
Nothing >>= saatJelas -- NothingJika salah satu langkah menghasilkan Nothing, seluruh rangkaian berhenti. Inilah cara Haskell menghindari "null pointer": tidak ada null check manual, hanya pengaliran nilai lewat bind.
Dengan do notation (dibahas detail di episode 11), contoh di atas ditulis lebih terbaca:
hitung :: Int -> Maybe Int
hitung n = do
a <- saatJelas n
b <- saatJelas a
pure (b + 1)Tip
Cara paling mudah mengingat tangga ini: Functor = nilai di dalam kotak bisa dipetakan (fmap). Applicative = fungsi di dalam kotak bisa diterapkan pada nilai di dalam kotak (<*>). Monad = hasil yang bergantung langkah sebelumnya (>>=), memungkinkan urutan yang bercabang. Di episode 11 kita akan melihat ketiganya bekerja pada konteks nyata: IO, Maybe, Either, dan State.
deriving: mencoba show atau == pada tipe tanpa instance Show/Eq → error kompilasi jelas, tinggal tambahkan deriving.fmap dan <*>: f <$> x untuk fungsi di luar konteks, f <*> x untuk fungsi di dalam konteks. Salah satu → error tipe yang menjelaskan letak masalahnya.return vs pure: di Monad, return adalah alias pure. Di kode modern, pure lebih disukai karena bekerja juga di Applicative.>>= padahal yang dibutuhkan <$>: pilih <$> untuk transformasi biasa, >>= saat langkah berikut bergantung hasil langkah sebelumnya.Inti yang harus dibawa pulang:
Eq, Ord, Show, Read, Num, lalu Functor, Applicative, Monad.deriving memberi instance otomatis untuk tipe data sederhana; instance manual untuk perilaku khusus.fmap memetakan ke dalam konteks; Applicative <*> menerapkan fungsi di dalam konteks; Monad >>= mengalirkan nilai berurutan.Di episode 9 selanjutnya kita akan membahas cabal & build tooling — struktur proyek cabal.project dan file .cabal, perintah cabal build/test/run, snapshot reproducible dengan stack (Stackage), serta setup HLS yang benar untuk editor kalian. Sampai jumpa di episode 9!