Menu

Choice dan Match di Zero: Tagged Union dan Pattern Matching

Cara choice mendeklarasikan tagged union di Zero dan cara match membuat percabangan menyeluruh atas variannya: versi Zero untuk sum type dan pattern matching.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Tagged Union ala Zero

choice mendeklarasikan tipe yang nilainya berupa salah satu dari beberapa varian bernama, dan setiap varian membawa payload-nya sendiri:

choice Result {
    ok:  i32,
    err: String,
}

Nilai Result adalah antara ok yang membawa i32 atau err yang membawa String. Tidak pernah keduanya, dan tidak pernah tidak keduanya. Sistem tipe memberi jaminan itu, dan match membuatnya mudah untuk diolah.

Ini ide yang sama dengan yang di bahasa lain disebut "tagged union", "sum type", "discriminated union", atau "algebraic data type". Zero menuliskannya sebagai choice dan menjaga grammar-nya tetap kecil.

Mendeklarasikan Choice

choice Name {
    variantA: PayloadTypeA,
    variantB: PayloadTypeB,
}

Setiap baris berisi satu varian. Namanya kamu tentukan sendiri, dan tipe setelah titik dua adalah payload yang dibawa varian itu. Varian yang tidak butuh payload memakai Void:

choice Token {
    word:   String,
    number: i32,
    eof:    Void,
}

Token.eof adalah varian tanpa payload yang berguna (tipe payload-nya Void), cocok untuk kasus penanda akhir.

Membuat Nilai Choice

Buat sebuah nilai dengan menyebut nama tipe, lalu variannya, lalu kirim payload-nya:

let success = Result.ok(42)
let failure = Result.err("validation failed")

Tipe payload harus cocok dengan payload yang dideklarasikan untuk varian itu. Result.ok("hello") akan menjadi error kompilasi karena ok mengharapkan i32.

Inferensi tipe juga berlaku di sini. Kalau sisi kanan sudah sepenuhnya menentukan tipenya, kamu bisa menulis let success = Result.ok(42), dan tipe binding itu adalah Result. Menulis anotasi tipe secara eksplisit juga boleh, kalau kamu ingin tipenya terdokumentasi di tempat binding:

let success: Result = Result.ok(42)

Mencocokkan Choice

match adalah cara membaca nilai choice. Bentuknya:

match value {
    .variantA => binding { /* body when value is variantA, with payload in `binding` */ }
    .variantB => binding { /* body when value is variantB */ }
}

Contoh lengkap dari repo resmi Zero. Klik Jalankan untuk melihat cabang .ok dijalankan:

Baca match itu apa adanya: "tergantung varian mana yang disimpan result, jalankan cabang yang cocok dan ikat payload-nya ke nama yang dipilih." Di cabang .ok, value adalah payload i32. Di cabang .err, message adalah payload String. Setiap cabang adalah scope tersendiri, dan binding-nya hanya terlihat di dalam isi cabangnya sendiri.

Menyeluruh (Exhaustiveness)

Inilah keuntungan besar match dibandingkan rangkaian if/else if: compiler memastikan setiap varian punya cabang. Kalau kamu lupa kasus .err, kamu tidak mendapat lompatan diam-diam ke cabang default saat runtime, melainkan error kompilasi:

{
    "code": "MAT001",
    "message": "match is not exhaustive: missing variant 'err'",
    "line": 9
}

(Kode error-nya hanya ilustrasi, tetapi prinsipnya adalah kontrak.)

Tambahkan varian baru ke choice itu, misalnya Result.timeout: Void, dan setiap match terhadap Result di codebase akan menjadi error kompilasi sampai kamu menangani kasus baru tersebut. Itu fitur, bukan beban: compiler memberi tahu persis di mana kasus baru itu perlu diperhatikan.

Saat Kamu Tidak Butuh Payload-nya

Kalau payload sebuah varian adalah Void atau kamu memang tidak peduli dengannya di cabang ini, kamu boleh mengabaikan binding-nya. Tetapi kamu tetap harus menulis cabangnya agar percabangannya tetap menyeluruh:

match token {
    .word   => w { /* use w */ }
    .number => n { /* use n */ }
    .eof    => _ { /* nothing to bind */ }
}

Penulisan persis untuk "abaikan payload" bisa berubah di Zero pra-1.0 (kamu mungkin melihat _ atau binding-nya tidak ditulis sama sekali). Konsepnya, yaitu setiap varian mendapat cabang, punya payload atau tidak, adalah bagian yang stabil.

Pola yang Umum

Tipe error bergaya Result

Inilah persis contoh yang dipakai repo resmi:

choice Result {
    ok:  i32,
    err: String,
}

Fungsi yang bisa berhasil dengan sebuah nilai atau gagal dengan sebuah pesan mengembalikan Result. Pemanggil memakai pattern matching untuk mengambil nilainya atau pesannya. Sistem raises/check di Zero menangani penerusan error untuk operasi yang bisa gagal, sedangkan Result berguna saat kamu ingin menyimpan nilai berhasil atau gagal sebagai data.

Token parser

choice Token {
    word:   String,
    number: i32,
    eof:    Void,
}

Sebuah tokenizer menghasilkan aliran Token. Setiap konsumen mencocokkan variannya untuk menentukan apa yang dilakukan: menampilkan kata, menjumlahkan angka, atau berhenti saat eof.

State machine

choice State {
    waiting:    Void,
    processing: i32,
    done:       String,
}

processing membawa ID tugas yang sedang dikerjakan, dan done membawa hasil akhirnya. Setiap transisi adalah nilai State yang baru, tanpa field yang bisa diubah tersebar di dalam sebuah shape.

Choice + Generic

choice bisa generic, sama seperti shape:

choice Maybe<T> {
    some: T,
    none: Void,
}

Maybe<i32> adalah "integer opsional". Maybe<String> adalah "string opsional". Pola yang sama muncul di standard library Zero dan jauh lebih cocok daripada nilai penanda null, karena kamu tidak mungkin lupa kasus .none begitu kamu memakai match terhadap tipe itu.

Kapan Memakai Choice, Shape, atau Enum

Rangkuman singkat dari shape dan enum:

  • Shape: record dengan beberapa field yang semuanya ada sekaligus.
  • Enum: salah satu dari N label, tanpa data tambahan.
  • Choice: salah satu dari N varian, masing-masing membawa payload.

Sebagian besar model data di program sungguhan adalah gabungan dari ketiganya. Kejelasan yang didapat dengan memulai dari pertanyaan "apakah ini dan, atau, atau atau dengan data?" adalah salah satu manfaat yang sering diremehkan dari bekerja dengan bahasa yang kecil.

Berikutnya: Capability World

choice dan match mencakup sisi data di Zero. Bab berikutnya membahas efek, yaitu cara program Zero berinteraksi dengan dunia luar. Bab itu dimulai dari capability World, objek yang menjadi gerbang setiap operasi I/O.

Pertanyaan yang sering diajukan

Apa itu choice di Zero?

choice adalah tipe tagged union di Zero, yaitu nilai yang berupa salah satu dari beberapa varian bernama, dan setiap varian membawa tipe payload-nya sendiri. Contoh: choice Result { ok: i32, err: String }. Nilai Result bisa berupa ok yang membawa i32 atau err yang membawa String. Kamu membuatnya dengan Result.ok(42) atau Result.err("bad").

Bagaimana cara kerja match di Zero?

match value { .variantA => binding { ...body } .variantB => binding { ...body } } membuat percabangan berdasarkan varian yang disimpan value. Setiap cabang mencocokkan satu varian, memberi nama binding untuk payload-nya, lalu menjalankan isinya. Compiler memastikan kamu sudah menangani setiap varian. Sifat menyeluruh inilah keunggulan utamanya dibandingkan if/else if.

Bagaimana cara membuat nilai choice?

Buat dengan menyebut nama tipe dan variannya, lalu kirim payload-nya: let r: Result = Result.ok(42) atau let r = Result.err("validation failed"). Tipe payload harus cocok dengan payload yang dideklarasikan untuk varian itu. Mengirim tipe yang salah adalah error kompilasi.

Apa bedanya choice dan enum?

Varian enum hanyalah label tanpa payload. Setiap varian choice membawa nilai dengan tipe yang sudah dideklarasikan. Kalau kamu perlu menempelkan data ke salah satu kasus (pesan error, hasil yang berhasil, token hasil parsing), pakai choice. Kalau kasusnya murni label, pakai enum.

Kenapa match lebih disarankan daripada if-else untuk choice?

match selalu menyeluruh: compiler memeriksa bahwa setiap varian ditangani, jadi kalau nanti kamu menambah varian baru, kamu dipaksa memperbarui setiap tempat yang membuat percabangan berdasarkan tipe itu. Rangkaian if/else if diam-diam meneruskan kasus yang tidak cocok, sehingga kasus yang hilang baru ketahuan saat muncul sebagai bug di production.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai