Belajar Haskell - Monads: IO, Maybe, Either, State
Episode 11 of 23

Belajar Haskell - Monads: IO, Maybe, Either, State

Menguasai monad sebagai alat komposisi efek di Haskell: memahami main :: IO () dan interaksi dengan dunia luar lewat >>= dan do notation, monad Maybe untuk null-safety, Either untuk error yang terstruktur, Reader Writer dan State untuk konfigurasi dan state lokal, serta peran MonadFail.

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

Pendahuluan

Di episode 8 kita menaiki tangga FunctorApplicativeMonad secara konseptual. Sekarang waktunya menghadapi monad yang paling nyata: IO, Maybe, Either, dan State. Episode ini adalah jembatan besar series ini — di sinilah kode Haskell berhenti menjadi "kalkulator" dan mulai menjadi program sungguhan yang membaca input, menghandle kegagalan, dan mengelola state.

Mengapa bab ini penting? Karena hampir semua pola kode Haskell produksi — membaca konfigurasi, memanggil database, memproses request HTTP — adalah komposisi monad. Setelah episode ini, kalian akan membaca kode do dengan lancar, memilih Maybe vs Either dengan tepat, dan tidak akan pernah bingung lagi dengan kata IO.

Monad IO: Gerbang ke Dunia Luar

IO adalah monad yang mewakili aksi berurutan yang berinteraksi dengan dunia luar. Program Haskell selalu bermuara di main :: IO ():

main adalah aksi IO
main :: IO ()
main = do
    putStrLn "Siapa nama kamu?"
    nama <- getLine
    putStrLn ("Halo, " ++ nama ++ "!")

Baca do seperti urutan aksi:

  • putStrLn "..." adalah aksi IO yang mencetak.
  • nama <- getLine menjalankan aksi getLine, lalu mengikat hasilnya ke nama.
  • Aksi terakhir menentukan hasil keseluruhan blok do.

Sintaks <- (dibaca "bind") adalah sintaks gula untuk >>=:

do notation setara dengan >>=
main :: IO ()
main = putStrLn "Siapa nama kamu?"
    >> getLine
    >>= \nama -> putStrLn ("Halo, " ++ nama ++ "!")

Keduanya identik — do hanya lebih mudah dibaca. Kunci pemahamannya: IO a adalah resep untuk menghasilkan nilai a dengan efek samping, dan >>= mengalirkan resep tersebut secara berurutan.

Yang Tidak Bisa Dilakukan IO

Aturan terpenting: nilai a di dalam IO a tidak bisa keluar ke kode pure. Tidak ada fungsi escapeIO :: IO a -> a. Inilah yang menjaga purity — dunia luar terkunci di balik gerbang IO, dan hanya fungsi bertanda IO yang boleh memasukinya.

Struktur aplikasi: pure core, IO boundary
main :: IO ()
main = do
    input <- getContents
    putStrLn (proses input)   -- proses :: String -> String (pure)
 
proses :: String -> String
proses = unlines . map (++ "!") . lines

Pola "pure core, IO boundary": semua logika dalam fungsi pure, IO hanya di tepian (baca input, cetak output). Ini pola arsitektur yang dipegang seluruh ekosistem, dan akan kita pakai lagi di episode 13 dan 15.

Monad Maybe: Null-Safety

Maybe a adalah Just a atau Nothing — jawaban Haskell untuk "nilai yang mungkin tidak ada":

Maybe: Just atau Nothing
lookupUmur :: String -> [(String, Int)] -> Maybe Int
lookupUmur nama db = lookup nama db

lookup mengembalikan Just umur jika ditemukan, Nothing jika tidak. Dengan do, rangkaian operasi yang bisa gagal berjalan tanpa null check manual:

Komposisi Maybe dengan do
data Pengguna = Pengguna { umur :: Int }
 
cekUsia :: Maybe Pengguna -> Maybe String
cekUsia maybeUser = do
    u <- maybeUser
    if umur u >= 17
        then Just "dewasa"
        else Nothing

Jika salah satu langkah menghasilkan Nothing, seluruh blok langsung menghasilkan Nothing — tidak ada "if null" bersarang seperti di Java/C#. Bahasa modern seperti Rust (Option) dan TypeScript (?) mengadopsi pola yang sama, tapi di Haskell pola ini bukan fitur tambahan — ia struktur dasar bahasa.

Monad Either: Error yang Terstruktur

Either e a adalah Left e (gagal, membawa error) atau Right a (sukses, membawa nilai). Berbeda dengan Maybe, Either menyimpan detail kegagalan:

Either: Left error, Right value
type ErrorMsg = String
 
divides :: Int -> Int -> Either ErrorMsg Int
divides _ 0 = Left "pembagian dengan nol"
divides x y = Right (x `div` y)

Kombinasikan beberapa operasi yang bisa gagal:

Rantai error dengan Either
hitung :: Int -> Int -> Either ErrorMsg Int
hitung a b = do
    x <- divides a b
    y <- divides x 2
    pure y

Pada kegagalan pertama, rantai berhenti dan error pertama di-Left-kan. Dengan Either String, error bisa dibaca manusia; dengan Either AppError (ADT), error menjadi data yang bisa diprogram — pendekatan yang kita perdalam di episode 16.

Tip

Aturan memilih: gunakan Maybe saat kegagalan bisa diabaikan tanpa penjelasan (misal "key tidak ada"), dan Either saat kalian perlu tahu kenapa gagal (misal validasi input). Di API nyata, Either hampir selalu pilihan yang benar karena error-nya bisa di-log dan dipetakan ke response HTTP.

Reader, Writer, dan State

Tiga monad "pengiring" yang membentuk fondasi banyak library:

Reader: Konfigurasi yang Dilewatkan

Reader r a mewakili komputasi yang membaca lingkungan r (konfigurasi, koneksi database) tanpa mempassing parameter secara manual:

Reader untuk konfigurasi
import Control.Monad.Reader
 
data Config = Config { dbUrl :: String }
 
getDbUrl :: Reader Config String
getDbUrl = asks dbUrl
 
run :: Reader Config String
run = do
    url <- getDbUrl
    pure ("konek ke " ++ url)

runReader run (Config "postgres://localhost") menghasilkan "konek ke postgres://localhost". Nilai Config "mengalir" ke semua komputasi di dalam Reader tanpa menyebutkannya berulang kali.

Writer: Logging Murni

Writer w a membawa output kumulatif (log) bersamaan dengan nilai:

Writer untuk logging
import Control.Monad.Writer
 
hitungDanLog :: Writer [String] Int
hitungDanLog = do
    tell ["mulai"]
    let r = 6 * 7
    tell ["hasil: " ++ show r]
    pure r

Hasilnya (42, ["mulai","hasil: 42"]) — nilai plus log, keduanya tanpa efek samping.

State: State Lokal yang Aman

State s a mewakili komputasi dengan state s yang di-thread secara eksplisit — cara Haskell "memiliki variabel mutable" tanpa melanggar purity:

State: state di-thread eksplisit
import Control.Monad.State
 
tambah :: Int -> State Int Int
tambah n = do
    s <- get
    put (s + n)
    pure (s + n)
 
main :: IO ()
main = print (runState (tambah 5 >> tambah 3) 0)  -- (8, 8)

get membaca state saat ini, put menimpanya. Karena state lewat secara eksplisit, siapa pun bisa menjalankan dua state-machine yang berbeda tanpa saling mengganggu — tidak ada global state di Haskell.

MonadFail: Pola Gagal di do

Pola Just x <- ... dalam blok do disebut pattern match that can fail. GHC menuntut monad tersebut menjadi instance MonadFail:

MonadFail - pola yang bisa gagal
f :: Maybe Int -> Maybe Int
f m = do
    Just x <- m      -- jika m = Nothing, seluruh blok Nothing
    pure (x + 1)

Karena Maybe adalah MonadFail, pola ini berjalan: Nothing diterjemahkan menjadi kegagalan. Ini adalah cara Haskell menangkap "asumsi yang mungkin salah" dengan aman — jika polanya tidak cocok, kegagalannya terstruktur, bukan crash.

Kesalahan Umum (Common Pitfalls)

  1. Mencoba "keluar" dari IO — tidak ada escapeIO. Semua program bermuara di main.
  2. return tidak menghentikan eksekusireturn x di Haskell hanyalah pure x: menghasilkan nilai dalam monad, bukan "return dari fungsi".
  3. Memakai Maybe padahal butuh informasi error — ganti ke Either.
  4. Mengikat nilai yang tidak dipakaix <- aksi padahal nilainya tidak dipakai; cukup aksi untuk aksi IO (), atau _ <- aksi.
  5. do tanpa monaddo membutuhkan konteks monad; jangan menulis do di fungsi pure.

Penutup

Inti yang harus dibawa pulang:

  • IO a adalah resep efek samping; nilai di dalamnya tidak bisa keluar ke kode pure.
  • do notation adalah gula untuk >>=; <- mengikat hasil aksi.
  • Maybe untuk nilai yang mungkin tidak ada (null-safety); Either menambahkan detail error.
  • Reader membaca konfigurasi, Writer mengumpulkan log, State me-thread state secara eksplisit.
  • MonadFail menangani pattern yang bisa gagal di dalam do dengan aman.

Di episode 12 selanjutnya kita akan membahas concurrency & STM — membuat thread ringan dengan forkIO, sinkronisasi dengan MVar, shared state yang aman dengan Software Transactional Memory, komposisi async dengan package async, serta lazy vs strict untuk concurrent pipeline. Sampai jumpa di episode 12!

Belajar Haskell - Monads: IO, Maybe, Either, State | Belajar Haskell