6 puan yazan GN⁺ 2024-02-26 | 1 yorum | WhatsApp'ta paylaş
  • Üretici ve tüketici kodu birbirleriyle veri alışverişi yaparken bir tarafı çağrılan işlev biçimine tersyüz ederek yazarsanız, başlangıçta görünen algoritma yapısı durum geçişlerinin içinde kolayca kaybolabilir
  • Knuth tarzı coroutine’ler, iki rutinin yürütme konumunu saklayıp denetimi birbirine devrettiği bir modeldir; ancak C’nin yığın tabanlı çağrı yapısı içinde bunu taşınabilir biçimde doğrudan uygulamak zordur
  • Bu yazının temel hilesi, bir switch alt bloğuna case yerleştirmeye izin veren C sözdizimini ve __LINE__ makrosunu kullanarak, return sonrasındaki konuma yeniden giren örtük bir durum makinesi oluşturmaktır
  • crBegin, crReturn, crFinish makrolarıyla açma çözücü ve ayrıştırıcının özgün döngü yapısı korunabilir; ancak korunması gereken yerel değişkenler static olmalı ve crReturn açık bir switch içinde ya da aynı satırda kullanılmamalıdır
  • Gerçek kodda, yeniden girilebilirlik ve çok iş parçacıklılık kısıtları nedeniyle bağlam yapısı geçiren geliştirilmiş bir biçime ihtiyaç vardır; coroutine.h hem basit scr makrolarını hem de yeniden girilebilir ccr makrolarını birlikte sunar

Üretici ve tüketiciyi bağlarken ortaya çıkan yapı sorunu

  • Büyük programlarda çoğu zaman bir kod veri üretir, başka bir kod da onu tüketir; bu durumda hangi tarafın çağıran, hangi tarafın çağrılan olacağı tasarımı zorlaştırır
  • Örnek iki küçük rutinden oluşur
    • Run-length sıkıştırma açma kodu, girdiyi getchar() ile okur ve karakterleri tek tek emit() ile çıkarır
    • Ayrıştırıcı kodu, karakterleri getchar() ile okuyup alfabetik ardışık bölümleri WORD, diğer karakterleri ise PUNCT olarak işler
  • İki rutin ayrı ayrı bakıldığında doğaldır; ancak açma çözücünün emit() çıktısının ayrıştırıcının getchar() girdisine doğrudan bağlanması için arada bir bağlantı yapısına ihtiyaç vardır
  • Bu, iki süreç ya da iki iş parçacığı arasındaki bir pipe ile de çözülebilir
    • Açma çözücünün emit() işlevi pipe’a yazar, ayrıştırıcının getchar() işlevi karşı taraftan okur
    • Bu yöntem basit ve sağlamdır; ancak ağırdır ve taşınabilirliği düşüktür, bu yüzden çoğu durumda basit bir iş için iş parçacıklarına bölmek istenmez

İşlevi yeniden yazmanın okunabilirlik kaybı

  • Geleneksel çözüm, iletişim kanalının bir ucunu çağrılabilir bir işlev biçiminde yeniden yazmaktır
  • Açma çözücüyü her çağrıda bir karakter döndüren bir işleve çevirirseniz, mevcut ayrıştırıcı getchar() yerine decompressor() çağırabilir
  • Tersine, ayrıştırıcıyı her karakter aldığında çağrılan bir işleve çevirirseniz, mevcut sıkıştırma açma kodu emit() yerine parser() çağırabilir
  • İkisini birden değiştirmek gerekmez; yalnızca bir tarafı değiştirmek de bağlantı için yeterlidir, fakat yeniden yazılan kod özgün hâlinden çok daha zor okunur
    • Özgün açma çözücü ve ayrıştırıcıda algoritma akışı döngülerin içinde doğal biçimde görünür
    • Yeniden yazılmış biçim ise static durum değişkenlerine ve switch durum geçişlerine dayanır; bu yüzden sıkıştırma biçimini veya ayrıştırıcı dilbilgisini koddan okumak zorlaşır
  • Amaç, iki taraftan hiçbirini açık bir durum makinesi gibi tersyüz edip yazmadan bağlamaktır

Knuth tarzı coroutine’ler ve C’nin sınırları

  • Donald Knuth’un coroutine çözümü, çağıran ve çağrılan ayrımını ortadan kaldırır ve iki süreci işbirliği yapan eşit varlıklar olarak ele alır
  • Bu modelin çağrı ilkesi normal işlev çağrısından farklıdır
    • Mevcut yürütme konumu yığında değil, ayrı bir yerde saklanır
    • Diğer rutinin son kaydettiği yürütme konumuna atlanır
    • Açma çözücü bir karakter yaydığında kendi program sayacını saklar ve ayrıştırıcının kaydedilmiş konumuna gider
    • Ayrıştırıcı bir sonraki karaktere ihtiyaç duyduğunda kendi program sayacını saklar ve açma çözücünün kaydedilmiş konumuna gider
  • Denetim, iki rutin arasında gerektiği kadar gidip gelir
  • Bu yöntem teorik olarak iyidir, ancak pratikte yalnızca assembly dilinde mümkündür
  • C gibi yüksek seviyeli diller yığın tabanlı yapıya dayanır; bu nedenle işlevler arası denetim aktarımında bir taraf çağıran, diğer taraf çağrılan olmak zorundadır
  • Taşınabilir C kodunda saf coroutine yaklaşımı, Unix pipe çözümü kadar pratik değildir

C’de “return and continue” taklidi

  • C’de gereken davranış, çağrılan işlevin return ettikten sonra bir sonraki çağrıda o return ifadesinin hemen sonrasındaki konumdan yürütmeye devam etmesidir; yani return and continue
  • Örneğin for (i = 0; i < 10; i++) return i; biçimindeki bir işlev 10 kez çağrıldığında 0’dan 9’a kadar sırayla döndürse ideal olur
  • İlk uygulama bir durum değişkeni ve goto kullanır
    • İşlevin başına ve her return sonrasına etiket konur
    • Çağrılar arasında korunan state değişkeni, bir sonraki devam etme etiketini gösterir
    • İşlev başında switch(state) ile uygun etikete gidilir
    • return hemen öncesinde, sonraki çağrıda dönülecek etiket state içine kaydedilir
  • Bu yöntem çalışır; ancak etiket yönetimi elle yapıldığı için bakım yükü büyüktür
    • Her return eklendiğinde yeni bir etiket oluşturmak ve başlangıçtaki switche de eklemek gerekir
    • Bir return kaldırıldığında karşılık gelen etiketi de kaldırmak gerekir
    • İşlev gövdesi ile switch listesinin tutarlılığını sürekli korumak gerekir

Duff’s device ile gizlenen durum makinesi

  • C’nin ünlü Duff’s device tekniği, switche karşılık gelen case ifadelerinin onun alt bloklarının içine de yerleştirilebilmesini sağlayan sözdiziminden yararlanır
  • Bu özellik coroutine hilesine uygulanınca, switch hangi gotonun çalışacağını seçmek yerine, switchin kendisi yeniden giriş atlaması gibi davranır
  • Temel biçim şöyledir
    • static int state bir sonraki devam noktasını saklar
    • İşlev başında switch(state) { case 0: ... } ile girilir
    • return hemen öncesinde sonraki case değeri state içine kaydedilir
    • İlgili case etiketi return ifadesinin hemen arkasına konur
  • Bunlar makrolarla sarıldığında coroutine gibi görünen bir arayüz ortaya çıkar
    • crBegin: static int state=0; switch(state) { case 0: kısmını gizler
    • crReturn: state değerini saklayıp bir değer döndürür, ardından aynı konuma bir case etiketi yerleştirir
    • crFinish: açık blokları kapatır
  • crReturn, do ... while(0) ile sarıldığı için if ile else arasında süslü parantez olmadan kullanıldığında da sözdizimi sorunu çıkmaz
  • Başta crReturn(1, i) gibi durum numarasını elle vermek gerekir; ancak ANSI C’nin __LINE__ makrosu kullanılırsa mevcut kaynak satırı numarası durum değeri olarak kullanılabilir
  • Bu iyileştirmeden sonra yalnızca crReturn(x) yazmak yeterlidir; fakat aynı satıra iki crReturn koymama kuralı eklenir

Makro kullanım kuralları ve örnekler

  • Makro tabanlı coroutine’ler birkaç kuralı varsayar
    • İşlev gövdesi crBegin ve crFinish ile sarılır
    • crReturn aşılırken korunması gereken yerel değişkenler static olarak bildirilir
    • Açık bir switch deyiminin içine kesinlikle crReturn konmaz
    • __LINE__ tabanlı uygulamada aynı satıra iki crReturn konmaz
  • Açma çözücü örneği, özgün döngü yapısını koruyarak karakter yayarken emit(c) yerine crReturn(c) kullanır
  • Ayrıştırıcı örneği, yeni bir karakter gerektiğinde crReturn() ile çağırana döner ve sonraki çağrıda parametre c içinde yeni karakteri almış olarak yürütmeye devam eder
  • Ayrıştırıcıda küçük bir yapı değişikliği vardır
    • İlk karakter işlev girişinde zaten c içinde bulunduğu için, özgün döngünün başındaki getchar()a karşılık gelen crReturn döngünün sonuna taşınır
    • İstenirse ayrıştırıcı için bir başlatma çağrısı gerektiği de kararlaştırılabilir
  • İki rutinin de coroutine makrolarıyla değiştirilmesi gerekmez; yalnızca bir tarafı değiştirip diğer tarafı çağıran olarak bırakmak da mümkündür
  • Sonuçta ANSI C, önişlemci ve switchin daha az kullanılan sözdizimi birleştirilerek üretici ile tüketici arasındaki veri aktarımı, açık bir durum makinesi olarak yeniden yazılmadan işlenir

Kodlama standartları ile algoritma açıklığı arasındaki çatışma

  • Bu teknik genel kodlama standartlarını ciddi biçimde ihlal eder
    • Makroların içine eşleşmeyen süslü parantezler girer
    • Alt blokların içinde case kullanılır
    • crReturn, switch, return ve case ifadelerini tek bir makronun içine gizler
  • Sözdizimsel yapıyı gizleyen makrolar, kodlama standartları açısından açıklığı zedeleyen unsurlar olarak görülebilir
  • Ancak açık bir durum makinesi olarak yeniden yazılmış işlevler de küçük case STATE blokları ve durum geçişlerinden oluşur; görsel yapı olarak goto etiket bloklarının sıralandığı bir işlevden çok farklı değildir
  • İşlev uzadıkça durum makinesi olarak yeniden yazmak, özgün algoritma yapısını daha fazla bozar
  • Bu teknik, sözdizimsel yapının bir bölümünü gizleme pahasına algoritma yapısını daha iyi görünür kılmaya çalışan bir ödünleşimdir

Yeniden girilebilir geliştirilmiş biçim ve sağlanan kod

  • Basit oyuncak uygulama static değişkenlere dayandığı için yeniden girilebilir değildir ve çok iş parçacıklı kullanım için de uygun değildir
  • Gerçek uygulamalarda aynı işlevin birden çok bağlamda çağrılabilmesi ve her bağlam için son return sonrasından yürütmeye devam edebilmesi gerekir
  • Geliştirme yöntemi, ek parametre olarak bir bağlam yapısı işaretçisi geçirmektir
    • Yerel durum ve coroutine durum değişkenlerinin tamamı yapı üyeleri olarak tutulur
    • Döngü sayaçları gibi değişkenlere de i yerine ctx->i şeklinde erişmek gerekir
    • Kod biraz daha çirkinleşir; ancak yeniden girilebilirlik sorununu ortadan kaldırırken rutinin genel yapısını korur
  • C++ kullanıcıları coroutine’i sınıf üyesi hâline getirip yerel değişkenlere karşılık gelen durumu sınıf içinde tutarak kapsamı daha doğal şekilde yönetebilir
  • Sağlanan coroutine.h, bu coroutine hilesini önceden tanımlı bir makro kümesi olarak uygular
    • scr önekli makrolar, static değişkenler kullanan basit biçimdir
    • ccr önekli makrolar, yeniden girilebilir gelişmiş biçimdir
    • Ayrıntılı belgeler başlık dosyasındaki yorumlarda bulunur
  • Visual C++ 6, varsayılan hata ayıklama ayarı olan “Program Database for Edit and Continue” altında __LINE__ makrosunu tuhaf biçimde ele aldığı için bu hileyi sevmez
    • VC++ 6’da coroutine kullanan bir programı derlemek için Edit and Continue kapatılmalıdır
    • Proje ayarlarında “C/C++” sekmesi, “General” kategorisi, “Debug info” ayarında “Program Database for Edit and Continue” dışında bir seçenek seçilmelidir
  • Başlık dosyası MIT lisansı ile sunulur

İlgili kaynaklar ve gerçek kullanım

  • Donald Knuth’un The Art of Computer Programming, Volume 1, Section 1.4.2 bölümü coroutine’leri saf biçimiyle ele alır
  • Tom Duff’ın Duff’s device tartışmasında, benzer bir coroutine hilesini bağımsız olarak akla getirmiş olabileceğini düşündüren içerik vardır; 2005-03-07 güncellemesinde Tom Duff bunu blog yorumu olarak doğrulamıştır
  • PuTTY içindeki SSH protokol kodu bu coroutine hilesini gerçekten kullanır
  • PuTTY örneği, ciddi üretim kodunda nadir görülen güçlü bir C hack’i düzeyindedir

1 yorum

 
GN⁺ 2024-02-26
Hacker News yorumları
  • Bir C projesinde API karmaşıklığını azaltmaya çalışırken bu sayfaya birkaç kez geri döndüm ve kontrol akışı açıklamasının harika olduğunu düşünüyorum
    Yığının içinde ve dışında durum saklama ile farklı yaklaşımların okunabilirlik farklarını daha net düşünmeme de yardımcı oldu
    Şu an vardığım sonuç, C coroutine kullanılıp kullanılmayacağına kütüphane kullanıcısının karar vermesinin daha iyi olduğu. Örneğin Mongoose(https://github.com/cesanta/mongoose) asenkronluğu olay callback’leriyle ele alıyor; böyle kütüphaneleri efsanevi çapraz platform C coroutine’lerine ya da daha kötüsü std::thread’e port etmeye çalışmaktansa, her sistemin thread/task temel öğeleriyle sarmalamak çok daha rahat

  • Coroutine gerçekten harika bir kavram; özellikle çoğunlukla Microsoft tarafındaki kişilerin sunduğu CppCon C++ coroutine videolarını izlemek de keyifli. “Negatif maliyetli soyutlama” kancası da epey iyi
    Birkaç yıl önce Meta’daki arkadaşlarım C++ coroutine kullanmaya başlamıştı; sonunda bunun büyük bir hata olduğunu söylediler. Derleyici implementasyon hatalarıyla uğraşmak zorunda kalmışlar ve bunları takip etmek oldukça pis bir iş olmuş olmalı. Google’da ise google3/ ile düzgün entegrasyonu yapan parlak kişilerin artık kullanılabilir olduğunu söylemesini bekliyoruz
    Bu yazı, Duff’ın cihazı [1] üzerinden makro tabanlı yapısal gotoyu bir C coroutine implementasyon stratejisi olarak açıklıyor. Kilit nokta, bir switch bloğunun neredeyse herhangi bir yerine case ifadeleri koyabilmek; tüm fonksiyonu switch ile sarmalayıp son coroutine dönüş konumunu bir static değişkende saklamak ve her coReturn’ü case olarak etiketlemek şeklinde
    Sustrik’in C coroutine yazısı da ilginç olabilir [2]
    [1] https://en.wikipedia.org/wiki/Duff%27s_device
    [2] https://250bpm.com/blog:48/index.html

    • Son birkaç yılda google3’ten fbcode’a geçmiş biri olarak, C++ kodunun çeşitli yerlerine co_yield, co_return, co_await gibi asenkron kodların serpiştirilmiş olmasının hem artıları hem eksileri var
      google3 içindeki yöntemle karşılaştırıldığında avantajı, kodu okurken her parçanın asenkronluğunun açıkça görülmesi. Google’daki bazı programcılar dallanma biriminin ötesinde threading modelini bilmeden yaşayıp sonradan ciddi bug’lar üretebiliyordu
      Dezavantajı daha basit. Çok sayıda kod “asenkron olabilir” hale geldiği için, zamanla sırf programcı o modda yazıyor diye her şey asenkronlaşıyor. Spinlock mu yoksa yield eden mutex mi kullanılacağı, kritik bölgenin boyutuna ve o andaki threading durumuna göre belirlenmeli; fakat okunabilirlik ve tutarlılığı korumaya çalışınca tüm projenin bir tarafa kayması kolay
      Varsayılanı iki taraftan birine koymadan, önceki çalıştırmanın profilini temel alarak bir sonraki çalıştırmayı daha iyi optimize eden, bunu da kod değişikliği ya da bug olmadan yapan threading dili implementasyonları hakkında daha çok şey öğrenmek isterim
    • Alternatif olarak GCC’nin labels as values özelliği kullanılabilir. Bir label’ın adresini alıp daha sonra oraya atlayabilirsiniz. 2005’te şu an lc-addrlabels.h içinde yer alan koda katkıda bulunmuştum
      GCC’nin yerel label özelliğini de kullanarak __LINE__ kullanımından tamamen kaçındım; aynı satıra birden fazla coReturn koymak da mümkündü
    • Duff’ın bir switch bloğunun neredeyse herhangi bir yerine case ifadesi konabileceğini fark ettiği söylenebilir ve bu muhtemelen doğru; ama özelliğin kendisi neredeyse kesin olarak bilinçli tasarlanmış bir özellik
      Yazının sonunda da belirtildiği gibi Duff, bunun üzerine coroutine kurulabileceğini de fark etmişti; ama bu fikri “iğrenç” bulmuştu
      C’nin switch yapısını ifade gücü düşük pattern matching gibi düşünürseniz “fallthrough” kolayca bug gibi görünür, ama öyle değil. Fortran’dakilerle aynı aileden hesaplanmış gotodur; değerlerin ardışık olması gerekmez ve label’ların hepsini en üste dizmek zorunda olmadığınız için daha kullanışlıdır. Böyle yazınca hesaplanmış COMEFROM’a daha yakın gibi de görünüyor
    • Ah, C önişlemcisi yıllar geçse de hediye vermeyi sürdürüyor :-(
  • “Yaygın kullanılan yüksek seviyeli diller coroutine desteklemiyor” sözü 2000’de doğru olabilir, ama bugün C++20, Lua, Python, Ruby gibi birçok dil destekliyor

    • Python 1991’de yaratıldı; dolayısıyla yield anahtar sözcüğü de o zaman ya da çok geçmeden ortaya çıkmış gibi geliyor
      Yazının sonundaki iyileştirme önerisi olan “ek bir fonksiyon argümanı olarak bir context struct pointer’ı koymak ve tüm yerel durum ile coroutine durum değişkenlerini bu struct’ın elemanları olarak bildirmek” yaklaşımı, closure implementasyonu gibi görünüyor. Çağrılan tarafı lambda yapıp dış değişkenleri/context’i/durumu kullanarak ne yapılacağına ya da hangi değerle yapılacağına karar verdirmek gibi; bunu böyle anlamam doğru mu merak ediyorum
    • Bu arada Simula67’de coroutine vardı. İlk değildi ama hatırladığım kadarıyla coroutine destekleyen ilk büyük dildi
  • switch yöntemi çok nadir değildir, ama genelde ilklendirme fonksiyonuna ve coroutine fonksiyonuna aktarılan bir durum işaretçisi bulunur
    Gömülü projelerde bu yöntemi çok kullandım; bir coroutine motorun hızlanma/yavaşlamasını yönetirken, başka bir coroutine yalnızca hangi yöne gidileceğini söylüyordu. Bir ağ kütüphanesinde[1] de kullandığım oldu. Standart kütüphanede de strtok()[2] gibi coroutine fonksiyonları var
    Yönetilebilir kılmak için makro cehennemine kadar gitmeye gerek yok, ama switch/case akışını okumaktan hiç keyif aldığımı hatırlamıyorum
    [1]: https://github.com/REONTeam/libmobile/blob/master/relay.c#L3...
    [2]: https://manpages.debian.org/bookworm/manpages-dev/strtok.3.e...

  • Aynı yazarın Simon Tatham's Portable Puzzle Collection’ı da var
    https://www.chiark.greenend.org.uk/~sgtatham/puzzles/

  • Bu size C kara büyüsü gibi görünüyorsa, aynı yazarın makrolarla keyfi kontrol yapıları oluşturma yazısı da okunmaya değer: https://www.chiark.greenend.org.uk/~sgtatham/mp/

    • Alt çizgi öneki yaklaşımı da hâlâ çoğu zaman isim gölgelemesine karşı hassas. Bundan kaçınmak için isimleri epey çirkin biçimde mangling yapmak gerekiyor; ayrıca ifade/deyim benzeri makroların aksine, dış blok makrolarında GNU/C23’ün hijyenik makro hack’leriyle bile bundan kaçınılamıyor
  • Coroutine’ler eğlenceli, ama gerçek kodda gerçek thread kullanmayı da düşünmek gerekir. Modern işlemcilerde çok sayıda çekirdek var, ancak coroutine’ler çoğu zaman yalnızca tek çekirdeği kullanır
    Bu pratikte de bir sorun. Yakın zamana kadar coroutine’leri yoğun kullanan qemu, blok aygıt I/O’sunun önemli bir bölümünü tek bir thread üzerinden gönderiyordu ve bu performans sorunlarına yol açıyordu. Kevin Wolf ve diğerleri bunu yıllar içinde düzeltti; modern qemu I/O için birden fazla thread kullanır hâle geldi ve bu çalışma RHEL 9.4’e girecek

    • Thread’ler ile coroutine’ler arasındaki tek bağlantı, bazı tek thread’li dil runtime’larının yalnızca coroutine sunması ve bu yüzden bazen thread’in daha iyi bir seçim olacağı yerlerde coroutine kullanılmasına yol açmasıdır
      Coroutine, tek thread’li yürütmeyi yapılandırmanın bir yoludur ve kendi başına yararlıdır. Metindeki üretici-tüketici kalıbı buna iyi bir örnektir; bir stream’i parser’a bağlamak paralel bir algoritma değildir, dolayısıyla thread bunu yazmak için işe yaramaz
      Paralel yürütülebilecek işler için tek thread’li bir paradigma kullanırsanız bunun verimsiz olması doğaldır; ama coroutine yoksul insanın paralelliği değil, bağımsız olarak anlamlı bir kontrol yapısıdır. Bir web sunucusunda event loop’un, dispatcher ile birlikte, birden çok bloklayan event’in arasından coroutine’leri geçirmesi gibi thread’lerle verimli biçimde birleştirilebilir; runtime çekirdek başına thread açarak paralelleştirdiğinde de thread’ler arası koordinasyon, her iş kuyruğunun derinliğini kontrol edip isteği daha az yoğun olana aktarmak düzeyine iner
    • Coroutine’lerin genelde yalnızca tek çekirdek kullanması çoğunlukla istenen davranıştır. Birbirinden ayrık paralel işlerse zaten doğal olarak farklı verilerle çalışırlar
      Coroutine fikri, yerel bir iş ve senkron veri olduğunda; bunu, çağıranın iç döngüyü “çektiği” fonksiyonel paradigmadan ziyade, fonksiyonun bir şeyler üzerinde döngü kurup sonuçları başka bir yerdeki soyutlanmış tüketiciye “ittiği” ters çevrilmiş biçimde ifade etmenin daha kolay olduğu durumlarda kullanmaktır
    • Thread’lerle coroutine’leri karıştırmak için oldukça iyi bir nokta var: Her thread’de bir coroutine zamanlayıcı örneği bulundurup, her çekirdek için bir thread oluşturmak
      Ardından coroutine’leri zamanlayıcılar arasında neredeyse hiç taşımamak ve farklı zamanlayıcılardaki coroutine’ler arasında veri paylaşımını da neredeyse hiç yapmamak
      Coroutine’ler, işbirlikçi zamanlama sayesinde hiç lock gerektirmeyen rahat bir eşzamanlılık programlama tarzı sağlar. Genelde zamanlama gecikmesi artar, ancak atomik işlem/lock overhead’i ortadan kalktığı ve preemptive zamanlama için timer sürekli yürütmeyi kesmediği için throughput oldukça yüksek olabilir
    • “Gerçek thread’leri düşünün” tavsiyesi genel olarak iyi değildir. Örneğin tek yapmak istediğiniz düz olmayan bir koleksiyon olan ağaç düğümlerini dolaşmak ise neden ayrı bir thread çalıştırmanız gerektiğini anlamıyorum
    • Coroutine’ler hafiftir ve senkronizasyonu çok kolaydır. Iterator veya tokenizer gibi küçük ölçekli artımlı hesaplamalar için tam uygundur. Muhtemelen aklınıza green thread gelmiş olmalı
  • Bu yaklaşımın C++ sürümü: https://www.codeproject.com/Tips/29524/Generators-in-C
    Ben de Sciter’ımda ne olur ne olmaz diye kullanıyorum; oldukça iyi çalışıyor ve kullanışlı

  • Bunu modüler ve güvenli biçimde başarmanın yolu muhtemelen effect handler’lardır. Python’daki yield’a benzer, ama değer döndürebilir; fonksiyon çağrısıyla sınırlı değildir ve istisnalar gibi scope’a sahiptir. Aşina değilseniz bu yazı iyi bir motivasyon sağlar
    Direct style’da yazılmış her fonksiyon, kontrolün başka bir yere gitmesi gerektiğinde bir “effect” gerçekleştirebilir. Burada c=getchar() ve emit(c) bu durumlardır
    Böylece kontrol effect handler’a gider; bu durumda muhtemelen iki fonksiyonun çağırıcısı sırada ne yapılacağına karar verir. Açıcı bir karakter yaydığında, o karakteri parser koduna verip devam ettirmek; parser daha fazlasına ihtiyaç duyduğunu söyleyene kadar ilerledikten sonra açıcıyı yeniden devam ettirmek gibi
    Effect’ler verimli uygulanabilir; özellikle continuation’ın yalnızca bir kez çağrılmasına izin verecek şekilde sınırlandırılırsa. OCaml’de durum böyledir. Direct style kod ile tip/bellek güvenliğini birlikte sağlar ve eşzamanlılık ortamlarında da çok kullanışlıdır
    Örnek burada: https://effekt-lang.org/docs/casestudies/lexer

  • “Bu numara elbette tüm kodlama standartlarını ihlal ediyor… Kodlama standartlarının yanlış olduğunu savunurum” kısmına hiç katılmıyorum
    Kodlama standartlarının bu kodu reddetmesi yanlış değil; bu kod yalnızca sevimli bir numaradan ibaret. Büyük ölçekli yazılım mühendisliği, sürprizleri ortadan kaldırma ve sabah 3’te çağrılıp hata ayıklamak zorunda kalan uykusuz kişinin bile okuyabileceği kod üretme işidir. Programcının her zaman dört temel kuralı hatırlamasını bekleyemeyiz
    switch, return, case gibi önemli öğeleri “obfuscation” makrolarının içine saklayıp sözdizimsel yapıyı bulanıklaştırırken algoritmik yapıyı ortaya çıkardığı iddiasını da ikna edici bulmak zor. İyi bir programda hem sözdizimsel yapı hem de algoritmik yapı net olmalı; bu yöntem bunun gerisinde kalıyor. Bence Rust’ın async fonksiyonlarda örtük durum makineleri oluşturma biçimi burada model alınmalı

    • En düşük ortak paydaya göre durmadan “aptallaştıran” tutum, günümüzde çoğu yazılım kalitesinin —ya da yokluğunun— nedeni. Bilgiden ve eğitimden kaçınırsanız sonunda bedelini ödersiniz
    • C’den C++’a geçince, neyi okunması kolay kod olarak gördükleri, yani gelecekteki okurun neyi anlaması gerektiğine dair beklentileri konusunda iki topluluk arasında büyük bir fark olduğunu gördüm
      C dünyasında üçlü koşul operatörü bile fazla kışkırtıcıdır, C99 da yeni moda bir şey muamelesi görür. C++ dünyasında template metaprogramming’den caydırmanın tek nedeni, kullandığınız standartta aynı işi constexpr ile yapabiliyor olmanızdır