Memproses data dalam jumlah besar tanpa memuat seluruhnya ke memori: streaming dengan Conduit dan library Streaming untuk lazy I/O, membangun internal DSL dengan bahasa ekspresif Haskell, serta parser teks dan konfigurasi yang robust dengan Megaparsec.

Setelah di episode 13 kalian berhasil membuat API HTTP, episode ini menjawab pertanyaan yang segera muncul begitu API kalian dipakai: bagaimana memproses data dalam jumlah besar? Log akses berukuran GB, file CSV jutaan baris, atau stream dari sistem lain — kalau diproses sebagai list penuh, program kalian akan kehabisan memori.
Mengapa bab ini penting? Karena laziness saja tidak cukup. Lazy evaluation memang menunda evaluasi, tapi thunk yang menumpuk justru bisa memicu space leak (episode 7). Streaming library memberikan resource management yang deterministik: setiap bagian data dibaca, diproses, dan dibuang tepat waktu — memori tetap konstan berapa pun ukuran input. Inilah keterampilan wajib untuk data pipeline di produksi.
Pola naif — membaca seluruh file menjadi list:
jumlahBarisNaif :: FilePath -> IO Int
jumlahBarisNaif fp = do
isi <- readFile fp -- seluruh file dimuat
pure (length (lines isi))Untuk file 2 GB, readFile — meski lazy di versi lama — tetap bermasalah: menyimpan isi penuh dalam thunk atau list lines. Di production, pendekatan ini adalah pintu menuju OOM. Solusinya: streaming.
Conduit adalah library streaming paling populer di ekosistem Haskell. Modelnya punya tiga bagian:
import Conduit
import qualified Data.ByteString as BS
hitungBaris :: FilePath -> IO Int
hitungBaris fp = do
n <- runConduitRes $
sourceFile fp -- Source: baca file per chunk
.| decodeUtf8C -- byte -> Text
.| linesUnboundedC -- Text -> baris
.| lengthC -- Sink: hitung jumlah
pure nAlur kanan-ke-kiri (.|) membaca: "ambil sumber file, pecah jadi baris, hitung". File tidak pernah dimuat penuh — hanya buffer kecil yang beredar. Memori konstan berapa pun ukuran file.
Conduit unggul untuk pipeline transformasi data. Contoh memproses log dengan filter dan transform:
import Conduit
import qualified Data.Text as T
ambilError :: FilePath -> IO ()
ambilError fp = runConduitRes $
sourceFile fp
.| decodeUtf8C
.| linesUnboundedC
.| filterC (T.isInfixOf "ERROR")
.| mapC (T.take 200)
.| sinkFileC "errors.txt"filterC dan mapC adalah versi streaming dari filter dan map — tetapi streaming, jadi ribuan baris pun diproses tanpa meledakkan memori. Struktur pipeline ini persis seperti rantai map/filter di episode 6, hanya kali ini diterapkan per-elemen tanpa membangun list penuh.
Tip
Memilih library streaming: Conduit untuk I/O yang sadar sumber daya (file, network) dengan ekosistem besar (termasuk integrasi database); Streaming untuk komposisi pure yang lebih elegan dan sesuai gaya lazy; dan pipes untuk abstraksi kategori yang paling murni. Semuanya menyelesaikan masalah yang sama — mulai dari Conduit karena paling sering ditemui di production.
Library Streaming menawarkan gaya yang lebih dekat dengan list biasa — streaming dinyatakan sebagai struktur data monadik yang bisa dikomposisi seperti list:
import Streaming
import qualified Streaming.Prelude as S
hitungBarisStreaming :: FilePath -> IO Int
hitungBarisStreaming fp = do
n <- S.length_ $ S.readFile fp & S.lines
pure nS.lines mengubah stream byte menjadi stream baris, dan S.length_ menghitungnya — semuanya streaming, dan komposisinya mirip fungsi list. Library Streaming dipilih ketika kalian ingin memakai seluruh idiom list (map, filter, fold) tetapi dengan jaminan streaming dan resource safety.
Haskell sangat pandai membangun internal DSL — bahasa khusus di dalam bahasa. Karena sintaksnya ekspresif (kombinator, operator, currying), DSL Haskell bisa dibaca seperti bahasa domain sendiri. Contoh nyata:
data Pipeline a
= Sumber [a]
| Filter (a -> Bool) (Pipeline a)
| Transform (a -> b) (Pipeline a) -- tipe disederhanakanMeski contoh ini sederhana, DSL lengkap untuk workflow, konfigurasi, atau test sudah jadi produk nyata di ekosistem Haskell — salah satunya adalah Hspec itu sendiri (episode 10), yang hanyalah DSL untuk menulis spesifikasi.
Megaparsec adalah library parsing paling populer untuk teks dan konfigurasi. Parser ditulis secara composable — parser kecil digabung menjadi parser besar:
{-# LANGUAGE OverloadedStrings #-}
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Void
import qualified Data.Text as T
type Parser = Parsec Void T.Text
data Config = Config { port :: Int, host :: String }
deriving (Show)
parseConfig :: Parser Config
parseConfig = do
_ <- string "port:"
p <- space1 *> decimal
newline
_ <- string "host:"
h <- space1 *> some (alphaNumChar <|> char '.')
pure (Config p h)
main :: IO ()
main = do
let input = "port:3000\nhost:localhost"
case parse parseConfig "config" (T.pack input) of
Left err -> putStrLn (errorBundlePretty err)
Right cfg -> print cfgKeunggulan Megaparsec: error yang informatif (lokasi baris/kolom, harapan vs kenyataan), dukungan indentation untuk format seperti YAML, dan composability penuh. Ini adalah dasar validasi input yang kita pakai lagi di episode 17 untuk keamanan.
Warning
Bedakan parsing dan validasi: parsing mengubah teks menjadi struktur data; validasi memeriksa struktur tersebut terhadap aturan domain. Mulailah dengan Megaparsec untuk syntax, lalu validasi semantik (misal "port harus 1024-65535") di fungsi pure terpisah. Pisahkan keduanya sejak awal agar error tetap jelas.
Menggabungkan semua materi, pipeline data Haskell yang sehat:
readFile untuk file besar — gunakan Conduit sourceFile atau readFileLazy dengan kesadaran penuh akan space leak.sinkList/S.toList_) — hanya untuk output kecil; kalau output juga besar, stream sampai ke sink.eof di akhir parser agar input ekstra terdeteksi.runConduitRes (atau bracket) agar handle file ditutup otomatis, termasuk saat ada exception.ByteString (binary) dan Text (unicode); String hanya untuk kode kecil.Inti yang harus dibawa pulang:
.| transformer .| Sink; runConduitRes menjamin resource safety.Di episode 15 selanjutnya kita akan membahas database & persistence — akses SQL langsung dengan postgresql-simple, ORM type-safe dengan Persistent, query SQL type-safe dengan Esqueleto, manajemen connection pool dengan resource-pool, serta migration untuk menjaga skema tetap sinkron. Sampai jumpa di episode 15!