4 puan yazan GN⁺ 2023-09-18 | 1 yorum | WhatsApp'ta paylaş
  • 19 Eylül 2023'te yayımlanan Java 21, record patterns ve switch pattern matching ile Kotlin, Rust ve C#'a yakın fonksiyonel pattern ifadelerini Java'nın içine taşıdı
  • Java 14'teki switch expressions, Java 16'daki records ve instanceof pattern matching, Java 17'deki sealed classes birikerek cebirsel veri tiplerini ele alacak temelin Java 21'de yerine oturmasını sağladı
  • records, final olma, immutable referanslar ve standartlaştırılmış getter'lar gibi kısıtlarla verilerin güvenli biçimde parçalarına ayrılmasını sağlar; record pattern ise iç içe verilerin switch içinde doğrudan çıkarılmasına imkân tanır
  • sealed classes/interfaces, yalnızca izin verilen alt tiplere açılarak sum type'a yakın bir model oluşturur; sealed interface ve record birlikte kullanıldığında RGB, CMYK, YUV, HSL gibi varyantlar sınırlandırılabilir
  • Java 21'de switch, null case ve when guard desteği de sunar; ancak hatalı record accessor'lar veya guard çalışırken oluşan istisnalar java.lang.MatchException ile sonuçlanabilir

Java 21'de kararlı hâle gelen pattern matching

  • Java 21, 19 Eylül 2023'te yayımlandı ve switch blokları ile switch ifadelerinde record patterns desteği sunuyor
  • Bu sözdizimi, Java'da da Kotlin, Rust ve C#'a benzer şekilde fonksiyonel programlama pattern'lerini ifade edebilmek için bir dönüm noktası olarak görülüyor
  • Son Java sürümlerindeki başlıca sözdizimi değişiklikleri Java 21'in pattern matching özelliğine bağlanıyor
    • Java 14: switch expressions kararlı hâle geldi
    • Java 16: records, instanceof pattern matching kararlı hâle geldi
    • Java 17: sealed classes kararlı hâle geldi
    • Java 21: record patterns, switch pattern matching kararlı hâle geldi
  • Bu değişiklikler bütünü sayesinde Java, daha önce ifade edilmesi zor olan cebirsel veri tiplerini (algebraic data types) ve bunların idiomatik kullanım biçimlerini ele alabilir hâle geldi

Tip teorisinde gerekli asgari kavramlar

  • Java 21'in özelliklerini anlamak için birkaç tip teorisi kavramı gerekiyor
  • bottom/empty type, hesaplanması mümkün olmayan değerler kümesini temsil eder; genel programlama dillerinde bu genellikle boş kümedir
    • Kotlin'deki Nothing'in constructor'ı private olduğu için bir instance'ı var olamaz
    • Java'daki Void'un constructor'ı private olsa da null taşıyabildiğinden gerçek bir bottom type olarak görülmesi zordur
    • Java'nın primitive void tipi değişken tipi olarak kullanılamadığı için bu açıdan daha yakın davranır
  • top type, tüm tiplerin tüm değerlerini temsil eden evrensel kümedir
    • Kotlin'de bu rolü Any üstlenir
    • Java'daki Object, primitive'ler nesne modelinden ayrı olduğu için diğer dillerdeki top type ile aynı anlamda görülmesi zordur
  • unit type, yalnızca tek değeri olan tiptir
    • Java'daki void, metot dönüşlerinde unit type gibi ele alınabilir; ancak parametre tipi olarak aktarılamaz
    • Kotlin'deki Unit, object olarak tanımlanır ve metot parametresi olarak da kullanılabilir
  • boolean type, true ve false olmak üzere iki değere sahiptir; nullable unit type olarak da ifade edilebilir ama pratik değildir

Product type ve Java records

  • product type, iki veya daha fazla bileşen tipi bir araya getiren tiptir; bileşen tiplerinin sayısı arity veya degree olur
  • C'deki struct, product type örneğidir
    • int, char *, double, int gibi bileşen tipleri tekrar edebilir
    • Tekrar eden tipler, alan adlarıyla birlikte ordered pair olarak düşünüldüğünde ayırt edilebilir
  • Python veya Rust'taki tuple'lar da product type olarak görülebilir; bu durumda bileşenlerin adlandırma rolünü indeks üstlenir
  • Java 16'da kararlı hâle gelen record class, product type için iyi bir örnektir
    • record alanları final'dır ve record miras alınamaz
    • record'un durumu oluşturma anında ayarlanır ve sonrasında korunur
    • Ancak record içine mutable data type konursa bunun içeriğinin değişmezliği de garanti edilmez
  • Normal Java class'larında public/private state, kalıtımdan kaynaklanan gizli state, mutable/static field ve standart dışı getter'lar karışabileceği için bileşenleri genelleştirmek zordur
  • records, aşağıdaki kısıtlarla pattern matching gibi dil özelliklerinin kararlı biçimde çalışacağı bir yapı garanti eder
    • record örtük olarak final class'tır ve miras alınamaz
    • java.lang.Record dışında bir class'ı extend edemez
    • record component'lerine visibility modifier eklenemez
    • component referansları her zaman final'dır ve immutable kabul edilir
    • Varsayılan getter, alan adını olduğu gibi kullanır; a alanının getter'ı a() olur
    • backing field örtük olarak private'tır ve getter üzerinden erişilir

record pattern ile iç içe verileri parçalama

  • Java 21'in switch pattern'i, instanceof kontrolünü ve açık cast'i tekrar etmeden iç içe record verilerini parçalarına ayırır
  • Örnekte record A(Record inner), record B(char b), record SomeOtherRecord() kullanılır
    • Eski yöntemde if (r instanceof A) sonrasında cast yapılır, ardından iç değer için yeniden instanceof ve cast tekrarlanırdı
    • switch pattern, case A(B(char a)) -> String.valueOf(a) gibi iç içe değerleri doğrudan çıkarır
  • switch bloğu, if-else ladder'a göre daha net bir yapıya sahiptir ve derin iç içe geçmiş verileri hızlıca çıkarmak için uygundur
  • Java 21'de doğrudan çalıştırmak için kodu main.java dosyasına koyup şu komutu kullanabilirsiniz
java --enable-preview --source 21 main.java

Sum type ve sealed classes/interfaces

  • Sınırlı seçenekleri ifade ederken Java enum kullanılabilir; ancak RGB, HSL, YUV, CMYK gibi farklı veri yapılarına sahip renk gösterimlerini yalnızca enum ile ele almak zahmetlidir
  • Kalıtım tabanlı polimorfizmle Color abstract class'ı ve RGB, CMYK, YUV, HSL class'ları oluşturulabilir; ancak normal class hierarchy açıktır
    • Kütüphane kullanıcısı RYB gibi yeni bir class oluşturup Color'ı miras alabilir
    • API genişlemeyi amaçlamıyorsa yeni varyant, crash'lere veya uzaktaki kodlarda ince hatalara yol açabilir
  • sealed classes, Java'da sum type kavramını ifade etmek için kullanılır
    • sum type, bir anda bileşenlerinden biri olabilen tiptir
    • tagged union type olarak da adlandırılır
  • sealed modifier'ı ve permits clause'u kullanılarak yalnızca belirli class'ların miras almasına izin verilebilir
public sealed class Color permits RGB, CMYK, YUV, HSL {
}
  • sealed class hierarchy'de doğrudan veya dolaylı mirasçıların sealed, non-sealed, final değerlerinden birine sahip olması gerekir; aksi hâlde compile error oluşur
    • sealed: Yalnızca permits içinde adı bulunan tipler miras alabilir
    • non-sealed: Normal class gibi miras alınabilir
    • final: Kalıtım ağacının leaf'idir, daha fazla genişletilemez

sealed interface ve record'u birlikte kullanma biçimi

  • switch pattern matching'deki destructuring records üzerinde çalışır; ancak records, Record dışında bir class'ı miras alamaz
  • Çözüm sealed interface kullanmaktır
    • sealed interface, sealed class'a benzer şekilde davranır
    • records ve enums da sealed interface'i implement edebilir
  • Örnekte Color, sealed interface olarak oluşturulur ve RGB, CMYK, YUV, HSL record olarak uygulanır
public sealed interface Color permits RGB, CMYK, YUV, HSL {
    String getDescription();
}

record RGB(int red, int green, int blue) implements Color {
    public String getDescription() {
        return "RGB Color: (" + red + ", " + green + ", " + blue + ")";
    }
}
  • Sonrasında switch içinde her record'un değerleri doğrudan çıkarılabilir
switch (color) {
  case RGB(int red, int green, int blue) -> {
  }
  case CMYK(double cyan, double magenta, double yellow, double black) -> {
  }
  case YUV(int y, int u, int v) -> {
  }
  case HSL hsl -> {
    System.out.println(hsl.getDescription());
  }
  case null -> {
    System.out.println("How did color become null?!");
  }
}
  • Java 21, switch blokları ve ifadelerinde null case işlemeyi desteklediğinden switch öncesinde ayrı bir null kontrolü yapmak gerekmez
  • Color sealed type ise Java tüm case'lerin ele alınıp alınmadığını bilebildiği için default case olmadan da exhaustive switch mümkündür

Guard clause ve when

  • Java 21, switch arm'larına ek koşul bağlayan guard clause desteği sunar
  • guard clause, when anahtar sözcüğüyle koşulu case label'a entegre eder
switch (color) {
  case RGB(int red, int green, int blue) when red > 200 -> {
    System.out.println("Very red.");
  }
  case RGB rgb when rgb.green > 100 -> {
    System.out.println("Sort of green...");
  }
  case RGB rgb -> {
    System.out.println("Not that red...");
  }
}
  • Önceden case RGB(...) gövdesinin içine yeniden if (red > 200) koymak gerekiyordu
  • Java, true olarak değerlendirilen ilk case'i eager biçimde eşleştirdiği için daha spesifik case'ler önce, daha az spesifik case'ler sonra konmalıdır
  • Guard eklenmiş RGB case'inden sonra exhaustive olmayı korumak için genel case RGB rgb gerekir

MatchException oluştuğu durumlar

  • Java 21'in pattern matching özelliğine ek olarak java.lang.MatchException da dahildir
  • record accessor bir istisna fırlatırsa switch pattern başarısız olur ve MatchException oluşabilir
record R(int i) {
    public int i() {
        return i / 0;
    }
}

static void exampleAnR(R r) {
    switch(r) {
        case R(var i): System.out.println(i);
    }
}
  • Yukarıdaki örnekte i() accessor'ı ArithmeticException fırlattığı için switch bloğu MatchException fırlatır
  • JEP 441'e göre her zaman istisna fırlatan record accessor son derece anormaldir; exhaustive pattern switch'in MatchException fırlatması da son derece istisnai bir durumdur
  • Exhaustive switch'te bile selector için belirtilen variant'lardan hiçbiri eşleşmezse istisna oluşabilir
    • JEP 441, enum için exhaustive switch'in eşleşmede başarısız olmasını, switch derlendikten sonra enum class'ın değiştiği durum olarak açıklar
  • Guard clause çalışırken istisna oluşsa da MatchException oluşabilir
static void example(Object obj) {
    switch (obj) {
        case R r when (r.i / 0 == 1): System.out.println("It's an R!");
        default: break;
    }
}

Kapsam dışında kalanlar

  • Java 21'in records, sealed types, switch pattern matching ve guard clause özellikleri birleştirildiğinde fonksiyonel programlamanın yapı taşları Java koduna uygulanabilir
  • generics'in switch patterns ile nasıl etkileştiği gibi bazı konular ele alınmıyor
  • Sonraki yazıda Java kodu yazma biçimini iyileştirmek için kullanılabilecek quirks ve pratik örnekler ele alınacak

1 yorum

 
GN⁺ 2023-09-18
Hacker News yorumları
  • Java 21’in en büyük özelliği sanal iş parçacıklarının yayımlanması: https://openjdk.org/jeps/444
    Nedense yazıda yer almıyor. Mevcut Go geliştiricilerini Java’ya çekebilecek bir özellik varsa bu olabilir; reaktif tarzda eşzamanlılık kalıplarından hoşlanmayanları da ikna edebilir gibi duruyor

    • Mevcut Go geliştiricilerinin Java’ya geri döneceğini sanmıyorum. Java ile 10 yıl çalıştıktan sonra Go’ya geçtim ve geri dönmeyi düşünmüyorum
      Çünkü Java uygulamaları ve kütüphaneleri; kalıtım, paketleme, nesne yönelimlilik, derleme araçları vb. yüzünden Go’ya kıyasla akıl yürütmesi ve anlaması çok daha zor
      Go basit; anlaması, okuması ve bakımını yapması kolay. Paketleme, bilgisayarda dosyaları tek bir klasörde düzenleme biçimine benziyor; araçlar da dilin içinde yerleşik. IntelliJ gibi bir IDE olmadan ancak zar zor kullanılabilir hale geliyormuş hissi de vermiyor
      Şimdi değişmiş olabilir ama bugünlerde gördüğüm Java kütüphanelerinin çoğu hâlâ böyle görünüyor
    • Java 21 sayesinde sanal iş parçacıkları eklendikten sonraki JRuby sürümünü bekliyorum. Charles Nutter, ağustostaki JRuby sunumunda bunun Ruby fiber’ları üzerindeki etkisini demoyla gösterdi; etkisi epey büyük
      JVM ve araç ekosisteminde sevdiğim çok şey var ama artık Java kodu yazmak pek hoşuma gitmiyor. JRuby iki tarafın avantajlarını bir ölçüde birlikte sunuyor
      Sunum burada; sanal iş parçacığı demosu yaklaşık 45. dakikadan itibaren başlıyor
      https://youtu.be/pzm6I4liJlg?si=GtxQ4MThEaNDfC67
    • Mevcut Golang geliştiricilerinin sırf bu özellik yüzünden Java’ya geçip geçmeyeceğinden pek emin değilim. Ama yön olarak, Go topluluğunda neden bu kadar az eşzamanlılık konteyneri varken Java topluluğunda bu kadar çok olduğunu merak ediyorum
      sync.Map bile Java’daki genel amaçlı ConcurrentMap gibi değil; iki belirli kullanım senaryosuna özelleştirilmiş. Java’da eşzamanlı kümeler, kuyruklar, bariyerler, phaser’lar, fork-join havuzları vb. var. Go rutinleri olsa bile bu konteynerler gayet işe yarar; en azından fork-join’in uygulanması o kadar basit değil. Her yerde mutex kullanmak fazla düşük seviyeli geliyor
      Üçüncü taraf uygulamaların olduğunu biliyorum ama eşzamanlılığı doğru yapmak çok zor; Java’daki JCTools veya Google Guava gibi olgun, çok sayıda kullanıcı ve geliştirici tarafından desteklenen bir seviyede değilse üçüncü taraf paketleri benimsemekte tereddüt ediyorum
    • Executor.newVirtualThreadPerTaskExecutor ile go arasındaki karşıtlık, Go geliştiricilerinin Java’ya geçmeyeceğini düşünmemin temel nedenini iyi gösteriyor
      Düzeltmek gerekirse aslında go yerine try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(...) } kullanmaya daha yakın
    • Java 21, yapısal eşzamanlılığı da önizleme olarak sunuyor (https://openjdk.org/jeps/453). Sanal iş parçacığı uygulamasını kullanıyor; yalnızca örneklere bakınca bile oldukça iyi görünüyor ve iş parçacığı tabanlı eşzamanlılıkla uğraşırken yaşanan birçok sıkıntıyı azaltıyor
  • Blog yazısının başlığının pek iyi seçilmediğini düşünüyorum. Gizli alt başlık “Java’da cebirsel veri tipleri” ama içeriği açıklamaya çok daha uygun olan bu. Daha iyi başlık Java 21’de cebirsel veri tipleri olurdu
    Muhtemelen başlık yüzünden buradaki yorumların önemli bir kısmı konudan sapmış. Cebirsel veri tipleri, Java uygulamasının artıları ve eksileri, diğer dillerle teknik karşılaştırmalar hakkında daha fazlasını görmek isterdim

    • Cebirsel veri tiplerinin daha popüler olduğu bir dil olmasını isterdim diye düşünüyorum ama Java’ya cebirsel veri tiplerinin eklenmesini gerçekten görmek isteyip istemediğimden emin değilim
      Mevcut Java kodu ortadan kalkmayacağına göre, bu tür kodlar rastgele karışınca gerçekten daha iyi mi olur emin değilim
    • Başta başlığı öyle koymuştum ama son anda değiştirdim; sonuçta yazının yönünü saptırmış gibi görünüyor
  • Burada açıklanan sealed classes özelliği bana tamamen yanlış bir şeymiş gibi geliyor
    Normal bir arayüz varsa herkes onu uygulayan yeni bir sınıf oluşturabilir; if (x instanceof Foo) { ... } else if (x instanceof Bar) { ... } gibi kodların, biri yeni bir sınıf eklediğinde çalışma zamanında bozulacağı mantığı kuruluyor. Bu yüzden yeni sealed interface özelliğiyle kimsenin o arayüzü uygulayan yeni sınıflar yapmasını engellerseniz if deyimi bozulmaz deniyor
    Ama nesne yönelimli programlama zaten bu problemi düşünüp çözmemiş miydi? Nesne yöneliminin artık modadan düştüğünü biliyorum ama Java nesne yönelimli bir dil
    Çözüm, arayüze bir metot ekleyip tüm sınıfların onu uygulamasını sağlamak. Böylece tüm seçenekleri dev bir if/switch deyiminde sıralamak yerine metodu çağırırsınız
    Bu yaklaşım, kodun genişletilmesini engellemekten daha iyi; aksine genişletilebilir kılar. Yeni uygulayıcı yalnızca o metodu uygular ve derleyici bunu zorunlu kıldığı için yanlışlıkla atlaması da mümkün olmaz
    Yazıdaki renk uzayı örneği (RGB, CMYK vb.) çok iyi bir karşı örnek. Renk uzaylarını kullanan kod yazmış olabilirsiniz ama kullanıcı ya da müşteri, sizin aklınıza gelmeyen tuhaf ve nadir bir renk uzayını kullanmak zorunda kalabilir. Dev bir if/switch deyiminde sıraladığınız renk uzaylarıyla sınırlı destek veren, bu yapı yüzünden genişletilemeyen kod yazmak istemem

    • Arayüze metot ekleme çözümü, gelecekte gerekecek tüm işlemleri önceden bilemiyorsanız sorunlu hale gelir
      Sealed classes bu problemi çözer. Ama bunun yerine “daha fazla genişletme sınıfına ihtiyaç olursa ve hepsini önceden bilemezsek?” diye yeni bir problem doğar. Sonuçta soru, ikisini birden elde etmenin bir yolu olup olmadığına dönüşür
      Bu probleme ifade problemi (expression problem) denir [1]
      İfade problemini çözebilen statik tipli diller var; Java da bunlardan biri [2]. Ancak Java’da bunu yapma biçimi hâlâ çok karmaşık ve zahmetli olduğundan neredeyse hiç kullanılmıyor. Haskell ya da JVM dünyasında kalmak istiyorsanız Scala bu konuda çok daha iyi
      [1] https://en.wikipedia.org/wiki/Expression_problem
      [2] https://koerbitz.me/posts/Solving-the-Expression-Problem-in-...
    • Sealed classes yaklaşımı da herkesin kodu genişletebilmesine izin verir, ancak arayüz metodu yaklaşımından farklı bir boyutta genişler
      Arayüz metotları ve sanal çağrı yaklaşımı, yeni sınıflar eklemek yerine yeni işlemler eklemek istediğinizde pek esnek değildir. Tek bir yeni işlem eklemek isteseniz bile tüm implementasyonlara gidip yeni metodu koymanız gerekir; erişim yetkiniz olmayan implementasyonları da bozabilirsiniz. Birbiriyle ilgisiz metotların da aynı sınıf içinde tanımlanması gerektiğinden kodun okunabilirliği ciddi biçimde düşer; sanal çağrı da bedava değildir, performansa etkisi vardır
      Bu durumda sealed class çok daha iyi ölçeklenir. Tek bir yerde yeni bir switch eklemek yeterlidir ve geriye dönük uyumluluğu da bozmaz
      Bu, meşhur ifade problemidir
      https://pkolaczk.github.io/in-defense-of-switch/
    • instanceof yerine polimorfik dispatch kullanılması önerisini anlıyorum; Bob Martin’in bunu uzun uzun anlattığını da gördüm ama katılmıyorum
      Böyle bir polimorfik dispatch için nesnenin birden fazla sorumluluğu kendi başına ele alması gerekir
      Bir video oyununda Car nesnesinde .render(), .collide(), .playSound() olabilir. Daha sonra Dog ekleseniz de bu üç metodu uygulamanız yeterlidir; Renderer, PhysicsEngine, SoundEngine üzerinde değişiklik yapmanız ya da onları yeniden derlemeniz gerekmez. Başka bir programcı da benim kıymetli koduma bug sokmadan entity ekleyebilir. Kulağa iyi geliyor
      Ama artık Car ve Dog grafik, fizik ve sesten haberdar olmak zorunda. Üstelik entity’ler yalıtılmış halde var olmaz. Arabalar ve köpekler doğru sırayla render edilmeli, birbirini örtebilmelidir. Çarpışmalar da karşılıklı kontrol edilmelidir. Gerçek bir game jam’de yaşadığım gibi, seslerden sorumlu kişinin ses davranışı eklemek için tüm nesnelerin içine girmek zorunda kaldığı durumlar da ortaya çıkar
      Bunun yerine fizik üzerine düşünürken Physics.collideAll() içinde çalışmak ve gerekirse instanceof ile özel işlem yapmak; grafik üzerine düşünürken de Graphics.renderAll() içinde çalışmak çok daha iyi
      Günlük backend Java web geliştirmesinde de durum benzer. Bir REST controller’da Java nesnesinin HTTP yanıtına nasıl dönüştürüleceğine karar verirken, her şeyi tek bir metotta görmek ve {instanceof Forbidden} olanı 403’e, {instanceof NotFound} olanı 404’e eşlemek daha iyi. Java sınıfının kendisine getCode() ya da REST’e özel içerik koymak istemem
    • Genişletmeye izin vermek her zaman mantıklı değildir. String’in final olmasının bir nedeni var; hatta final varsayılan olmalı, alt sınıflandırmaya izin verilecek sınıflar da açıkça open olarak belirtilmeli denebilir
      Fonksiyonel programlamada toplam tiplerin tipik örneği listedir. Burada yalnızca Element(T head, List tail) ve Nil() vardır. Bunu genişletmek için bir neden yoktur; hatta genişletilirse listeyle çalışan tüm fonksiyonlarla birlikte hatalı koda dönüşebilir
      Ayrıca desen eşlemeye benzeyen visitor pattern çok laf kalabalığı yaratır ve Java’nın normal metot dispatch semantiğini kullanan bir hack’e dayanır. Burada desen eşlemenin birkaç kat daha okunabilir olduğunu düşünüyorum
    • Bu tür özelliklerin geçerli kullanım alanları var
      Örneğin güvenlik token’larını doğrulayan bir güvenlik arayüzü düşünülebilir
      Normal bir arayüz olsaydı, onu uygulayıp token’ı yok saymak (her şeye izin vermek), token’ı sızdırmak ya da backdoor eklemek kolay olurdu. Böyle bir sınıf güvenlik kontrolünün yapıldığı yere enjekte edilirse güvenlik delinebilir
      Sealed interface olduğunda onaylanmamış yeni bir implementasyon var olamaz. O arayüzü uyguladığını iddia eden bir nesne aldıysanız, bunun gerçekten güvenlik kontrolünü yapan doğrulanmış implementasyonlardan biri olduğu garanti edilir. Böylece güvenlik hataları ve exploit’lerden oluşan bir sınıfı tamamen ortadan kaldırmış olursunuz
  • Sum type’ları bilen ama Java’daki sum type’ları pek bilmeyen biri olarak iyi bir yazı.
    Ancak yalnızca sum type’larla Java’yı sevmeye başlayıp başlamayacağımı bilmiyorum. Geniş kapsamlı null olasılığı hâlâ duruyor ve bu yazıda da birkaç kez kendini gösteriyor.

    • Java’da null olasılığı büyük bir sorun, ancak anotasyon tabanlı null olasılığı framework’leri etkili ve ekosistemin geneline yayılmış durumda. Kişisel olarak neredeyse zorunlu görüyorum.
      Google, Meta, Microsoft gibi şirketlerin @Nullable’dan başlayarak anotasyonları standartlaştırmaya çalıştığı https://jspecify.dev/ projesini gerçekten heyecanla bekliyorum.
    • Valhalla geldiğinde açık null olasılığı da geleceği için bu sorun da ele alınmış olacak.
    • Hâlâ ifade odaklı bir dil de değil.
  • “Neden product type deniyor?” sorusuna yazının verdiği yanıt yanlış değil, ama daha sezgisel ve kısa söylemek gerekirse product type’ta olası değerlerin toplam sayısı, bileşen tiplerin olası değer sayılarının çarpımıdır.
    product’ı sum ile değiştirince de aynı şey geçerlidir.
    İlginç biçimde, a -> b biçimindeki benzersiz fonksiyonların toplam sayısı yalnızca girdi ve çıktıya bakıldığında üs alma ile bulunabilir. Yani (b’nin olası değer sayısı) ^ (a’nın olası değer sayısı)dır.

    • Daha sezgisel ve kısa söylemek gerekirse, product type kümelerin Kartezyen çarpımına denktir.
    • Map’ler ve listeler de üstel tip için başka örneklerdir. Saf fonksiyonlar teorik olarak önceden hesaplanmış değerlere yönelik bir map lookup ile değiştirilebildiğinden, bunların fonksiyonlarla aynı olduğu sezgisi doğaldır. Bu bağlamda liste, anahtarı tamsayı olan özel bir map’tir.
      Matematiksel olarak yazarsak, Bool listesinin sol tarafı öğe sayısı, sağ tarafı ise toplam olasılık sayısıdır.
      0 : 1
      1 : 2
      2 : 4
      3 : 8
      4 : 16
      5 : 32
      Böyle devam eder.
  • Project Valhalla’nın tamamlanıp Java’ya sonunda value type’ların gelmesini bekliyorum. O zaman sum type’lar, value type’lar ve coroutine’lerle birlikte oldukça iyi dillerden biri olacak gibi.

  • Java aslında hiçbir zaman gerçekten kötü bir dil olmadı.
    Sorun insanlardaydı. Devasa aşırı mühendislik, kod tabanını anlamayı zorlaştıran çok fazla soyut kavram, ters GOTO ifadeleri gibi çalışan anotasyon biçimindeki kod büyüleri ve DI framework’leri sorundu.
    Düzeltilmesi gereken dil değil, ekosistem. Java ekosistemi içinde bir tür “reform” hareketine ihtiyaç var.
    Kotlin, Clojure, Scala’ya geçmek tek başına yeterli değil.

    • Herhangi bir dilde HammerFactory üreten bir HammerFactoryFactory oluşturabilirsiniz. Ama Java ekosistemi bu tür problem çözme biçimini teşvik edip cesaretlendiriyor. C#’ın da benzer olduğunu düşünüyorum.
      Java’nın gerçekten ihtiyaç duyduğu tek şey bağımsız fonksiyonlar ya da namespace’li fonksiyonlar. Bazen sınıfa gerek yoktur; bir modül ya da namespace içindeki fonksiyon yeterlidir. Bunun neden mümkün olmadığını anlamıyorum.
  • Yazar, Record’ların neden gerekli olduğunu açıklarken çoğu Java nesnesinin tüm alanları private tuttuğunu ve bunlara yalnızca okuma-yazma accessor metotlarıyla erişildiğini belirtiyor.
    Ancak accessor’ları tanımlayan dil düzeyinde zorunlu bir kural olmadığından, foo için getter adını getBar koysanız da çalışır; fakat bar’a erişmeye çalışan kişiyi kafa karışıklığına sürükleyebilir.
    Scala, unapply metodunu implement eden nesneler için pattern matching destekler. Bu yaklaşım zararlı mı görülüyor? Java neden bu yolu izlemedi?

    • Bu da yine bir standardizasyon meselesi. Java’nın standardizasyonu C++ gibi yavaş. record pattern JEP’inin son dipnotuna bakınca unapply benzeri bir şeyin hazırlıkta olabileceğini ima ediyor; yani umut henüz tamamen tükenmiş değil.
  • Java her zaman harika bir dildi. İnsanı kusturacak noktaya getiren şey enterprise tarzı ekosistem. Tek satırlık bir mantığı implement etmek için onlarca sınıf ve interface kullanıldığını gördüm.