Menu

Zero Choice ve Match: Tagged Union'lar ve Pattern Matching

Zero'da choice bir tagged union'u nasıl bildirir ve match variant'ları üzerinde nasıl exhaustive dallanır — Zero'nun sum type ve pattern matching versiyonu.

Bu sayfada çalıştırılabilir editörler var — düzenle, çalıştır ve sonucu anında gör.

Zero Tarzında Tagged Union'lar

choice, değeri birkaç isimlendirilmiş variant'tan biri olan ve her variant'ın kendi payload'ını taşıdığı bir tipi bildirir:

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

Bir Result değeri ya bir i32 taşıyan bir ok ya da bir String taşıyan bir err'dir. Asla ikisi birden, asla hiçbiri değil. Type system bu garantiyi verir ve match üzerine işlemi rahatlatır.

Bu, diğer dillerin "tagged union", "sum type", "discriminated union" ya da "algebraic data type" dediği fikrin aynısı. Zero bunu choice olarak adlandırır ve grameri küçük tutar.

Bir Choice Tanımlamak

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

Her satır bir variant listeler. Ad sizindir; iki nokta üst üstesinden sonraki tip, o variant'ın taşıdığı payload'dır. Payload'a ihtiyaç duymayan bir variant Void kullanır:

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

Token.eof, kullanışlı payload'ı olmayan (payload tipi Void) bir variant — terminator tarzı case'ler için faydalı.

Bir Choice Değeri Oluşturmak

Tipi, ardından variant'ı adlandırıp payload'ı geçirerek bir değer inşa edin:

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

Payload tipi, variant'ın tanımlanan payload'ıyla eşleşmek zorundadır. Result.ok("hello") bir compile error olur çünkü ok bir i32 bekler.

Tip çıkarımı burada da çalışır. Sağ taraf tipi tamamen sabitliyorsa let success = Result.ok(42) yazabilirsiniz ve bağlamanın tipi Result'tır. Bağlama yerinde tipi belgelemek için explicit annotate etmek de iyidir:

let success: Result = Result.ok(42)

Bir Choice'ı Match'lemek

match, bir choice değerini okuma yolunuzdur. Şekil:

match value {
    .variantA => binding { /* value variantA olduğunda gövde, payload `binding`'de */ }
    .variantB => binding { /* value variantB olduğunda gövde */ }
}

Resmi Zero repo'sundan işlenmiş bir örnek — .ok kolunun çalıştığını görmek için Run'a tıklayın:

match'i tam anlamıyla okuyun: "result'ın hangi variant'ı tuttuğuna göre eşleşen kolu çalıştır ve payload'ı seçilen ada bağla." .ok kolunda value, i32 payload'ıdır. .err kolunda message, String payload'ıdır. Her kol ayrı bir scope'tur; bağlama yalnızca kendi gövdesinin içinde görünür.

Exhaustiveness

Bu, match'in if/else if zincirlerine göre büyük kazanımıdır: compiler her variant'ın bir kola sahip olduğunu doğrular. .err case'ini unutursanız bir varsayılan dala runtime fall-through almazsınız — bir compile error alırsınız:

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

(Error code illüstratiftir; prensip sözleşmedir.)

Choice'a yeni bir variant ekleyin — örneğin Result.timeout: Void — ve codebase'deki Result üzerindeki her match, yeni case'i ele alana kadar bir compile error olur. Bu bir özveri değil, bir özellik: compiler size yeni case'in tam olarak nerede dikkat ihtiyaç duyduğunu söyler.

Payload'a İhtiyaç Duymadığınızda

Bir variant'ın payload'ı Void ise ya da bu kolda umursamıyorsanız, bağlamayı yok sayabilirsiniz — ama exhaustiveness'i karşılamak için yine kolu yazmak zorundasınız:

match token {
    .word   => w { /* w'yi kullan */ }
    .number => n { /* n'yi kullan */ }
    .eof    => _ { /* bağlanacak hiçbir şey yok */ }
}

"Payload'ı yok say" için kesin yazım pre-1.0 Zero'da gelişebilir (ya _ görürsünüz ya da bağlamayı tamamen atlarsınız). Kavramsal nokta — her variant payload olsun olmasın bir kol alır — kararlı kısımdır.

Yaygın Desenler

Result tarzı bir error tipi

Resmi repo'nun kullandığı örneğin tam olarak aynısı:

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

Değerle başarılı olabilen ya da mesajla başarısız olabilen fonksiyonlar bir Result döndürür. Çağıranlar değeri veya mesajı çıkarmak için pattern match yapar. Zero'nun raises/check sistemi başarısızlığa açık işlemlerin yansıtılmasını ele alır; Result, bir başarı-ya-da-başarısızlık değerini veri olarak tutmak istediğinizde faydalıdır.

Bir parser token'ı

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

Bir tokenizer bir Token stream'i üretir. Her tüketici, ne yapacağına karar vermek için variant üzerinde match yapar — kelimeyi yazdır, sayıyı topla, eof'ta çık.

Bir state machine

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

processing mevcut görev ID'sini taşır; done nihai sonucu taşır. Her geçiş yeni bir State değeridir — bir shape'in içine serpilmiş mutable alanlar yok.

Choice + Generics

choice, shape gibi generic olabilir:

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

Maybe<i32> "opsiyonel bir integer"dır. Maybe<String> "opsiyonel bir string"dir. Aynı desen Zero standart kütüphanesinde gösterilir ve bir null sentinel değerinden çok daha iyi uyar — tipe karşı match yaptığınızda .none case'ini unutmanız mümkün değildir.

Choice ile Shape ile Enum Arasında Ne Zaman Seçim Yapılır

Shape'ler ve enum'lardan hızlı bir özet:

  • Shape — birden fazla alana sahip bir record, hepsi birlikte mevcut.
  • Enum — N etiketten biri, ekstra veri yok.
  • Choice — N variant'tan biri, her biri bir payload taşıyor.

Gerçek bir programdaki çoğu veri modeli bu üçünün bir kombinasyonudur. "Bu ve mi, veya mu, yoksa veriyle veya mı?" sorusundan başlamanın berraklığı, küçük bir dilde çalışmanın hafife alınan faydalarından biridir.

Sırada: World Capability

choice ve match, Zero'nun veri tarafını kapsar. Bir sonraki bölüm effect'ler hakkında — Zero programlarının dış dünyayla nasıl etkileşime girdiği. Her I/O parçasının kapısını tutan obje olan World capability ile başlıyor.

Sıkça Sorulan Sorular

Zero'da choice nedir?

Bir choice, Zero'nun tagged-union tipidir — birkaç isimlendirilmiş variant'tan biri olan ve her variant'ın kendi payload tipini taşıdığı bir değer. Örnek: choice Result { ok: i32, err: String }. Bir Result değeri ya bir i32 taşıyan bir ok'tur ya da bir String taşıyan bir err. Result.ok(42) veya Result.err("bad") ile oluştururs unuz.

Zero'da match nasıl çalışır?

match value { .variantA => binding { ...body } .variantB => binding { ...body } }, value'nun hangi variant'ı tuttuğuna göre dallanır. Her kol bir variant'ı pattern match eder, payload bağlamasını adlandırır ve gövdesini çalıştırır. Compiler her variant'ı kapsadığınızı doğrular — exhaustiveness, if/else if üzerindeki ana faydadır.

Bir choice değeri nasıl inşa edilir?

Tipi ve variant'ı adlandırıp payload'ı geçirerek oluşturun: let r: Result = Result.ok(42) ya da let r = Result.err("validation failed"). Payload tipi, variant'ın tanımlanan payload'ıyla eşleşmek zorundadır — yanlış tipi geçmek bir compile error'dır.

choice ile enum arasındaki fark nedir?

enum variant'ları payload'sız, yalnızca etikettir. choice variant'larının her biri tanımlanan bir tipte bir değer taşır. Case'lerden birine veri eklemeniz gerekiyorsa (bir hata mesajı, başarılı bir sonuç, parse edilmiş bir token) choice kullanın. Case'ler saf etiketse enum kullanın.

Choice'lar için match neden if-else'e tercih edilir?

match doğası gereği exhaustive'tir — compiler her variant'ın ele alındığını kontrol eder, dolayısıyla sonradan yeni bir variant eklemek tip üzerinde dallanan her yeri güncellemeye zorlar. Bir if/else if zinciri ise sessizce düşer; eksik case'i production'da bir bug olarak ortaya çıkana kadar gizler.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA