Menu
Playground'da Dene

Zero World Capability: Global Olmadan Explicit I/O

Zero'nun global stdout'u, ambient filesystem'i, implicit network'ü yoktur. Dış dünyaya dokunan her şey main'e geçirilen bir World capability üzerinden akar. İşte neden ve nasıl.

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

Tek Cümleyle Fikir

Zero programları I/O'yu ambient bir global'e başvurarak değil, ona izin verilerek yaparlar.

O izin, World tipinde bir değerdir. Runtime main'i çağırmadan önce bir tane oluşturur ve programınız dış dünyayla etkileşime girmesi gereken her yere onu (veya parçalarını) iletir.

Neden Global Yok?

Çoğu dil, herhangi bir yerde herhangi bir fonksiyonun stdout'a yazmasına veya bir dosya açmasına izin verir. JavaScript'in console.log'u, Python'un print'i, C'nin printf'i var. Kolaylık gerçek, ama maliyeti de öyle: bir fonksiyonun imzasından I/O yapıp yapmayacağını bilemezsiniz. Bilmek için gövdeyi — özyinelemeli olarak — okumanız gerekir.

Zero farklı bir tutum sergiler. Global bir print yok. Ambient bir os.Stdout yok. Fonksiyonunuz I/O yapıyorsa, bu gerçeğin imzasında görünmesi gerekir; çünkü I/O yapmanın tek yolu size bir capability verilmiş olmasıdır.

Faydalar üç yerde görülür:

  1. Bir imzayı okumak size fonksiyonun ne yapabileceğini söyler. World'ten söz etmeyen bir fonksiyon stdout'a yazamaz, bir socket açamaz, bir dosya okuyamaz. Type system bunu zorunlu bir garanti yapar.
  2. Pure kodu test etmek önemsiz hale gelir. Pure fonksiyonların print'i stub'lamaya ya da filesystem'i mock'lamaya ihtiyacı yoktur — bunlara erişimleri zaten yoktur.
  3. Ajanlar yerel muhakeme yapabilir. Zero kodu üreten veya onaran bir AI ajanı, baktığı bir fonksiyonun effect'leri olup olmadığını — tüm codebase'i okumadan — bilebilir.

Kanonik Kullanım

Temel şekli zaten hello-world'den gördünüz:

Üç şey oluyor:

  • main parametresini world: World olarak bildiriyor. Runtime programa bir World değeri verir ve onu burada bağlar.
  • world.out, World capability'sinin bir alanı olarak görünen standart çıktı stream'idir.
  • world.out.write(...) bir string yazar. Başarısız olabilen bir değer döndürür (yazma başarısız olabilir); check bunu yansıtır.

Parametreyi yeniden adlandırabilirsiniz — w: World ya da io: World — ama world konvansiyondur ve geniş Zero ekosistemiyle tutarlılık için tutmaya değer.

World Üzerinde Neler Yaşar

World capability'si, bir programın ihtiyaç duyabileceği yüzeyleri sergiler. Kesin şekil runtime'ın neyi desteklediğine bağlıdır, ama şu girdileri bekleyebilirsiniz:

  • world.out — standart çıktı.
  • world.err — standart hata.
  • world.in — standart girdi.
  • Dosya açmak, environment variable okumak ve network üzerinden bağlanmak için bir yol.

Alanların yetkili listesi için güncel Zero standart kütüphane dokümanlarına bakın. Bazı yüzeyler (network, filesystem) en üst seviye yerine World aracılığıyla erişilebilen daha dar capability tipleri arkasında yaşayabilir.

Capability'leri Kod Boyunca Aktarmak

Yakaladığınız — explicit effect'ler için ödediğiniz bedel — şu: I/O yapması gereken herhangi bir fonksiyonun capability'i alması gerekir. Onu implicit olarak elde edemezsiniz.

fun log(world: World, message: String) -> Void raises {
    check world.out.write(message)
}

pub fun main(world: World) -> Void raises {
    log(world, "starting\n")
    log(world, "done\n")
}

log, üzerinden yazabilmesi için world alır. log world almasaydı, gövde world.out.write çağıramazdı — bağlama mevcut olmazdı.

Bu, ambient I/O'su olan bir dildekinden daha fazla parametre taşımak demek. Karşılığında main'in tüm çağrı grafiği artık yalnızca imzalardan görünür hale gelir:

  • main world alır, dolayısıyla I/O yapabilir.
  • log world alır, dolayısıyla I/O yapabilir.
  • İmzasında world olmayan herhangi bir fonksiyon yapamaz.

Daha Dar Capability'ler

Tüm World'ü her fonksiyona geçirmek kaba bir yaklaşımdır — root yetkilerini dağıtmak gibi. Zero'nun teşvik ettiği desen, yalnızca gerçekten ihtiyacınız olan World kesitini almaktır:

fun log(out: Stream, message: String) -> Void raises {
    check out.write(message)
}

pub fun main(world: World) -> Void raises {
    log(world.out, "starting\n")
    log(world.out, "done\n")
}

Şimdi log yalnızca bir Stream'e erişiyor (world.out'un sergilediği aynı tip). Onun üzerinden yazabilir ama bir dosya açamaz veya network'ten okuyamaz. Çağıran, log'un ne yapmasına izin verdiğini seçti.

Gerçek Zero kodunda göreceğiniz kesin tip adları (Stream, Writer, capability kesitleri), toolchain sürümünüzdeki standart kütüphanenin sözlüğünü takip edecek. Desen — maksimumu değil minimumu geç — evrenseldir.

Pure Fonksiyonlar

World'e ihtiyaç duymayan fonksiyonlar World almamalıdır. Bu kısmen bir stil tercihi, kısmen de type system tarafından zorunlu kılınır: bir capability olmadan I/O yapmanın yolu yoktur.

fun sum(point: Point) -> i32 {
    return point.x + point.y
}

sum, dış dünyaya göre pure'dur. İmzaya bakan bir çağıran, bu fonksiyonun hiçbir şey yazdırmayacağından, bir dosya açmayacağından, bir sunucuya ping atmayacağından kesinlikle emindir. Bu, bir static analyzer'ın (veya bir ajanın) gövdeyi okumadan güvenebileceği bir özellik.

Capability'ler ve raises

Bir capability üzerindeki hemen hemen her işlem başarısızlığa açıktır. world.out.write, stream kapalı olduğu için başarısız olabilir. Bir dosya açma, dosya mevcut olmadığı için başarısız olabilir. Capability API yüzeyi raises ve check ile eşleştirilmiştir — başarısızlığa açık işlemler başarısızlık modlarını imzalarında bildirir ve çağıranlar bunları check ile kabul eder.

Bu kombinasyon Zero'nun effect hikayesinin kalbidir:

  • Ne olabilir → raises { ... }.
  • Ne üzerindenWorld (veya onun bir kesiti).
  • Nerede → capability ve raises'in görünür olduğu her yerde.

Bu, bir fonksiyonun effect'leri hakkında yalnızca imzasından kesin muhakeme yapmak için yeterli bilgidir.

Test Üzerine Bir Not

Capability tabanlı I/O test yapmayı doğası gereği basitleştirir. Test altındaki bir fonksiyonun çıktısını yakalamak ister misiniz? Ona, kendisinden ne yazmasının istendiğini kaydeden sahte bir out capability'si geçirin. Pure olması gereken bir fonksiyonu test etmek ister misiniz? Ona herhangi bir capability geçirmeyin — tip imzası dış dünyaya dokunmasına izin vermeyecektir.

Standart kütüphane tam olarak bu amaç için sahte veya in-memory capability'ler kuran test harness'ları sağlayabilir. Kesin API dille birlikte gelişecek; prensip (capability'ler ikame edebileceğiniz değerlerdir) ise asıl koldur.

Sırada: Raises ve Check

World effect hikayesinin yarısıdır — bir fonksiyonun dokunabileceği yüzeyler. Diğer yarısı başarısızlık: bir şey ters gittiğinde nasıl yansır? Bu, sırada Raises ve Check'te ele alınıyor.

Sıkça Sorulan Sorular

Zero'da World nedir?

World, runtime tarafından sağlanan capability nesnesidir; bir Zero programına dış dünyaya erişim verir: stdout, stdin, dosyalar, network, environment variable'lar vb. Runtime bir World değeri oluşturur ve onu main'e geçirir. I/O yapması gereken fonksiyonlara World (veya onun daha dar bir kesiti) geçirilmek zorundadır — global bir kaçış kapısı yoktur.

main neden World parametresi alır?

Zero'nun ambient global'leri yoktur. printf, console.log veya os.Stdout gibi herhangi bir fonksiyonun izin almadan çağırabileceği bir muadil yoktur. Runtime main'e bir World capability verir; main (ve onun çağırdığı her fonksiyon) yalnızca o değer üzerinden I/O yapabilir. Bu, her effect'i bir fonksiyonun imzasında görünür kılar.

Capability tabanlı I/O normal I/O'dan nasıl farklıdır?

Çoğu dilde I/O implicit'tir — herhangi bir fonksiyon herhangi bir anda stdout'a yazabilir veya filesystem'den okuyabilir. Capability tabanlı I/O ise I/O yapma iznini bir değer haline getirir: kullanmak için size bir World (ya da onun bir kesiti) verilmiş olması gerekir. Pure hesaplama fonksiyonları World almaz ve dolayısıyla literal olarak I/O yapamaz; bunu type system zorlar.

Çağrı yığınımın derinliklerinde World'u implicit olarak alabilir miyim?

Hayır, bilinçli olarak. Çağrı yığınının derinliklerindeki bir yardımcı stdout'a yazmak zorundaysa, ona World'ü — ya da daha dar bir capability'i — explicit olarak geçmek zorundasınız. Bu, ambient I/O'su olan bir dildekinden daha fazla parametre taşımak demek; ama bir fonksiyonun imzasını okuyup I/O yapıp yapmayacağını bilmenin maliyeti bu.

world.out.write ne yapar?

world.out.write("text\n"), verilen string'i runtime'ın sağladığı capability üzerinden programın standart çıktı stream'ine yazar. Başarısız olabilen bir değer döndürür — yazma başarısız olabilir — bu yüzden çağrıyı check ile sarıp hatayı çağrı yığınında yukarı yansıtırsınız.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA