2 puan yazan GN⁺ 2025-10-24 | 1 yorum | WhatsApp'ta paylaş
  • Bu yazı, mülakat ortamında bir adayın basit bir FizzBuzz problemini lambda hesabı tabanlı saf fonksiyon kombinatorleriyle çözmeye çalıştığı hicivli bir anlatı sunuyor
  • Baş kahraman JavaScript ile başlıyor, ancak giderek S, K, I kombinatorlerini tanımlayarak dilin temel yapısını kendi başına yeniden kuruyor
  • Ardından boolean'ları, sayıları, listeleri ve string'leri tamamen kombinatorlerle ifade ediyor ve Y kombinatorü aracılığıyla özyinelemeyi kuruyor
  • Sonunda FizzBuzz'ı tamamen point-free tarzda tamamlıyor, ancak kod insanın anlayamayacağı ölçüde büyüyüp genişliyor
  • Yazı, programlama dillerinin özü ile soyutlamanın uç noktalarını mizahi bir şekilde incelerken, geliştirici kültürünün öz-hicvini de ortaya koyuyor

Mülakatın başlangıcı: FizzBuzz ve kombinatorler

  • Hikâye, mülakatçı Dana'nın adaya FizzBuzz problemini çözmesini istemesiyle başlıyor
    • Aday, sıradan bir döngü yerine S ve K kombinatorlerini tanımlayarak çözmeye başlıyor
    • Dana şaşırıyor, ama aday “biraz daha sürer” diyerek devam ediyor
  • Aday, I, B, C, W, T gibi çeşitli kombinatorler tanımlıyor ve bunları yeni bir dilin temel yapı taşları olarak kullanıyor
    • Her kombinator, fonksiyon bileşimi, argüman değiştirme, kendine uygulama gibi lambda hesabının temel kavramlarını gerçekleştiriyor
    • Kod yorumlarında “Bluebird, Cardinal, Warbler, Thrush” gibi kombinator adlarının takma adları da geçiyor

Boolean ve sayıların tanımı

  • Aday, TRUE, FALSE, NOT ifadelerini yalnızca kombinatorlerle tanımlayarak boolean mantığını kuruyor
    • Dana “bu lambda hesabı mı?” diye soruyor, ancak aday “lambda hesabı fazla şişkin” diye yanıt veriyor
  • Ardından ZERO, SUCC, PRED, IS_ZERO gibi tanımlar yaparak sayı sistemini (Church numeralleri) kuruyor
    • SUCC ardıl, PRED öncül, DECREMENT ise 0'ın altına inmeyen azaltma işlemini gerçekleştiriyor
    • ONE'dan TEN'e kadar sayılar sırayla tanımlanıyor

Özyineleme ve Y kombinatorü

  • Aday, ADD fonksiyonunu tanımlıyor ve bunu giderek point-free biçime dönüştürüyor
    • ADD_MAKER tanımlanıyor ve özyinelemeyi mümkün kılmak için bunun etrafına Y kombinatorü sarılıyor
    • Dana, “JavaScript'te de özyineleme var” diye itiraz ediyor, ancak aday “yakında artık JavaScript olmayacak” diye cevap veriyor
  • JavaScript'in katı değerlendirmesi (eager evaluation) nedeniyle stack overflow oluşunca aday kodu Skoobert adlı “tembel (lazy)” bir JS yorumlayıcısında çalıştırıyor
    • Sonuçta başarıyla 3 çıktısı alınıyor

Aritmetik, listeler ve string'ler

  • Aday, SUBTRACT, MULTIPLY, MOD, DIVIDE gibi tüm aritmetik işlemleri de kombinator tabanlı olarak kuruyor
    • Her işlem, IS_ZERO, PRED, SUCC gibi yapıların özyinelemeli birleşimleriyle oluşturuluyor
  • Ardından CONS, FIRST, REST, EMPTY tanımlarıyla liste yapısını kuruyor
    • MAP, FOLD, RANGE, CONCAT gibi yüksek dereceli işlevsel işlemler de tamamen kombinator biçiminde uygulanıyor
  • Listeyi string olarak yazdırmak için renderList, showLines fonksiyonları yazılıyor
    • Dana artık çoktan pes etmiş gibi görünüyor ve tepki vermiyor

Karakter ve string'lerin kurulumu

  • Aday, karakter kodlarını 1'den 9'a ve ardından 10'luk birimler halinde kombinatorler kullanarak tanımlıyor
    • CHAR_A'dan CHAR_Z'ye ve CHAR_0'dan CHAR_9'a kadar her şey sayı kombinasyonlarıyla ifade ediliyor
  • ARRAY fonksiyonu aracılığıyla string'ler listeye dönüştürülüyor ve FIZZ, BUZZ, FIZZBUZZ oluşturuluyor
    • extractString fonksiyonu sayesinde liste gerçek string'e çevriliyor
    • Konsol çıktısı "fizzbuzz" oluyor

Sayıdan string'e dönüşüm

  • Sayıyı basamak listesine çeviren NUMBER_TO_DIGIT_LIST ve bunu string'e dönüştüren NUMBER_TO_STRING tanımlanıyor
    • DIVIDE, MOD, TEN kullanılarak her basamak ayrıştırılıyor
    • DIGITS_NUMERAL listesi aracılığıyla rakam-karakter eşlemesi yapılıyor
  • Bu süreçle birlikte sayı → karakter → string dönüşümü tamamen kombinator sisteminin içinde mümkün hale geliyor

FizzBuzz'ın tamamlanışı

  • FIFTEEN, ONE_HUNDRED tanımlanıyor ve MAP ile RANGE kullanılarak 1'den 100'e kadar bir liste üretiliyor
    • Her sayı için 3, 5 ve 15'in katları belirlenerek "Fizz", "Buzz", "FizzBuzz" ya da sayı string'i yazdırılıyor
    • showLines(extractString)(FIZZBUZZ_RESULT) ile nihai sonuç ekrana basılıyor
  • Dana, “şimdi memnun musun?” diye soruyor, ancak aday tüm değişken tanımlarını kaldırıp kodu yalnızca saf kombinatorlerle yeniden yazıyor
    • Sonuç, binlerce satıra ulaşan ve yalnızca S ile K'den oluşan devasa bir ifade oluyor

Sonuç ve anlamı

  • Yazı, programlama dillerinin temel yapı taşlarını incelerken aynı zamanda geliştiricilerin basit problemleri gereksiz yere aşırı karmaşık hale getirme eğilimini hicvediyor
    • “Hiçlikten daha azıyla programlamak” başlığı, dünyayı dilin en küçük birimlerinden yeniden kurma girişimini simgeliyor
  • Bu eser, fonksiyonel programlama, lambda hesabı ve kombinator mantığı üzerine derin bir kavrayışı mizah ve anlatıyla işleyen teknik bir edebiyat örneği
    • Aynı zamanda “FizzBuzz'ı bile felsefi bir deneye dönüştüren geliştirici zihniyetini” hicivli biçimde ortaya koyuyor

1 yorum

 
GN⁺ 2025-10-24
Hacker News görüşleri
  • İnsanlar “bu da ne şimdi?” diye düşünüyor gibi göründüğü için combinator kavramının özünü açıklayayım
    combinator, global durumu değiştirmeyen ve dış değişkenleri capture etmeyen bir fonksiyondur. Aynı argümanlar verilirse her zaman aynı sonucu üretir ve sistemin geri kalanını hiçbir şekilde etkilemez
    Bu tür saf fonksiyonlar birleştirildiğinde yine başka bir combinator oluşur. Yani saf fonksiyonların bileşimi hâlâ saf fonksiyondur
    Bu fonksiyonlar assembly ya da C ile de kolayca yazılabilir. Örneğin x64 üzerinde S ve K’yi doğrudan implemente edip, onun üstüne combinator tabanlı olarak Common Lisp derlerseniz, sonuçta Lisp sadece iki assembly fonksiyonunun üzerinde çalışıyor olur
    Performansı pek iyi olmaz ama, sık kullanılan birkaç yüz kadar kalıbı inline combinator olarak isimlendirirseniz oldukça kullanışlı bir “makine” elde edersiniz
    Bu yapı, mutation’dan çekinen Forth’a, bir bytecode VM’e ya da combinator’lara “komut” diyen bir CPU’ya da benzer
    Sonuçta combinator, implementasyon ayrıntılarını mümkün olduğunca göz ardı ederek uygulamalı bilgisayar biliminin bir ifadesi olarak görülebilir. Bu yüzden lambda calculus’tan daha basit olduğu söylenebilir
    Eğer lambda calculus’u birkaç combinator ile implemente edip onun üstüne Lisp kurarsanız, yığının en alt katmanında yapılması gereken makineye bağımlı işler son derece azalır

    • Sanki bir yerlerde bir fonksiyon kendi kendini çağırmış ve bir seed round yatırımı almış gibi hissediyorum
    • Öğrenciyken buna benzer bir şey yapmıştım (Lisp değil, macro expansion ile lambda calculus’a açılan basit bir dildi) ve her şeyin yalnızca S ve K ile kurulabiliyor olması gerçekten sarsıcıydı. Ama aynı zamanda ne kadar verimsiz olduğuna da şaşırmıştım. Yine de komut kümesi büyüdükçe durum çok daha iyi hale geliyor
    • Teoride hepsi mümkün ama pratikte grafik yeniden yazım yerine çok daha kullanışlı bir hesaplama temeli seçmek gerekir. Hesaplanabilirliğin sınırlarını denemiyorsanız, bu neredeyse bir parti numarası seviyesinde kalıyor. Üstelik sayıların temsili de başlı başına bir sorun. En azından two’s complement tamsayılar ve verimli destructor’lar olmadan etkileyici sayılmaz
    • Acaba gerçekten combinator üzerinde implemente edilmiş bir Lisp var mı diye merak ediyorum. Varsa port etmek epey eğlenceli olabilir
  • let A = (x) => (y) => (z) => x(z)(y((w) => z))
    

    Bunu birkaç kez birleştirmeniz yeterli

    let K = A(A(A))(A(A(A))(A)(A)(A)(A)(A);
    let S = A(A(A(A)(A(A)(A(A)))(A(A(A(A)((A(A)))))))(A)(A);
    

    “Ben lambda calculus falan asla kullanmam. Fazla şişirilmiş bir dil.”
    Ama aslında combinatory logic daha da şişkindir. Aynı programı ifade etmek için daha fazla bit gerekir
    Lambda calculus ise tam tersine en özlü dillerden biridir
    JavaScript gibi eager dillerde bile argümanları dummy lambda’larla sararak lazy davranış elde edebilirsiniz. Örnek için bu JS BLC yorumlayıcısına bakın
    İlgili makaleler için bu PDF ve IOCCC örneğine göz atabilirsiniz

    • İlginç olan şu ki, en hızlı lambda calculus yorumlayıcılarından bazıları (örneğin uni.c) aslında lambda ifadelerini combinatory logic’e dönüştürerek hesaplama yapıyor. Sadece S ve K’den daha büyük bir temel küme kullanıyorlar
    • “bloated language”, gereksiz özelliklerle dolu bir dili ifade eder. Örneğin C++, C’den daha kısa kod yazdırabilir ama buna rağmen çok daha karmaşık bir dildir
    • O kod bloğuna bakınca ağzımdan çıkan sesler neredeyse argüman listesiyle birebir örtüşüyor
    • K ve S tanımlarında parantez hatası var gibi görünüyor. Düzeltilmiş hâli şöyle
      let K = A(A(A))(A(A(A))(A)(A)(A)(A)(A));
      let S = A(A(A(A)(A(A)(A(A)))(A(A(A(A)(A(A)))))))(A)(A);
      
      Ayrıca eager bir dili lazy hale getirmek de şu şekilde mümkün
      let A = x => y => z => () => x()(z())(y()(w => z()));
      
    • “w de ne?” diye düşünmeden edemiyorum
  • “FizzBuzz biliyor musun?”
    “Elbette. 10 karakterlik code golf sürümünü mü, junior’ın da anlayabileceği 12 satırlık sürümü mü, yoksa acayip bilgi gösterisi için yapılmış tamamen absürt sürümü mü?”

    • Bunu görünce C64 BASIC ile en hızlı FizzBuzz’ı yazmayı denedim. Sabahımı eğlenceli biçimde boşa harcadım
      sonuç bağlantısı
  • combinator logic açıklaması gerçekten harikaydı. Yazım tarzı bana bu seriyi hatırlattı

    • Açıklamadan çok gösteri amaçlı bir performans gibiydi. Yine de oldukça etkileyiciydi
    • Kesinlikle o seriden ilham alınmış gibi duruyor. Yazarın adını anmış olsaydı daha iyi olurdu
    • Birleşik Krallık’ta Online Safety Act yüzünden erişim engelli olduğu için üzücü
  • Bir zamanlar okuduğum “FizzBuzz in TensorFlow” yazısını hatırlattı
    bağlantı

    • Bu yazı en azından takip edilebilir durumdaydı, o hoşuma gitti
  • Bu tür programlama mülakatı çerçevesi biraz yanlış geliyor
    Mülakatın amacı (1) adayın programlama bilgisini ölçmek, (2) şirketin programlama kültürüne uyumunu görmek olmalı
    Örneğin JavaScript pozisyonu için alım yapıyorsanız ve biri combinatory logic’e fazlasıyla dalmışsa, kültürel açıdan pek uygun olmayabilir. Bu yüzden elenmesi de garip olmaz

    • Muhtemelen bu seriye gönderme yapıyor. Ama asıl metinde bu açıkça belirtilmiyor
    • Bazen HN yorumcuları eğlenmeyi unutmuş insanlar gibi geliyor
    • Programlamadaki çeşitliliği vurgulaması doğru, ama bu sonuçta belirli bir ekosistemde uzmanlık seçmenin başka bir biçimi. Çoğu zaman da legacy code ya da mevcut ekosistemi kullanma ihtiyacından kaynaklanıyor
    • “IQ’su düşükse elensin” gibi laflar komik ama günün sonunda yeterince para verirseniz her türlü OOP/FP/FRP tarzına uyum sağlanır
    • Gerçek hayatta bu tür ideal mülakatlar neredeyse hiç yok. Çoğu, kendi dünya görüşünü dayatan hayal kırıklığına uğramış mülakatçılardan oluşuyor. Gerçek işin mülakatta konuşulanlarla pek ilgisi olmuyor
  • Moses Schönfinkel’i hatırlamanın zamanı geldi
    combinatory logic’i 1920’de Hilbert’in öğrencisiyken icat etmişti. Rusya’ya döndükten sonra ruhsal hastalıklarla mücadele etti ve Moskova’da yoksulluk içinde öldü. Wikipedia’ya göre makaleleri komşuları tarafından yakacak olarak kullanılmış

  • Son kod bloğu, kaydırma çubuğu çıkacak kadar büyük (166KB)
    S ve K, curried fonksiyonlardır; her seferinde yalnızca bir argüman alırlar
    Ayrıntılar için bu StackOverflow yanıtına bakın

    • Aşağı kaydırırken syntax highlighting’in pes ettiği an en komik kısımdı
  • “Bluebird, cardinal, warbler, thrush” gibi kuş isimlerine bayıldım. Bunu yeni bir isimlendirme kuralı yapmak istiyorum
    “Barendregt. Church artık fazla ana akım.”
    “Yakında bu artık JavaScript de olmayacak.”
    Sanki Douglas Adams, SICP anlatıyor gibi

    • Kuş isimleri Smullyan’ın 『To Mock a Mockingbird』 kitabından geliyor. Orada çok daha fazla kuş var
  • “Bu... Y combinator mı?”
    “Aynen. Özyineleme için gerekiyor.”
    Eğlenceli bilgi: sabit nokta combinator’ları (fixed-point combinator) sonsuz tanedir. Yani Y combinator olmadan da özyinelemeyi implemente etmenin sonsuz sayıda yolu vardır