İçeriğe geç
Muhammet Şafak
Diller 3 dk okuma

Go 1.18 generic'ler: uzun bekleyişin sonuna doğru

Go 1.18 ile gelen generic desteği bir dilin nasıl yeniden şekillenebileceğini gösteriyor. İlk izlenimler ve trade-off'lar.


Go topluluğu yıllardır generic tartışıyordu. Her büyük Go konferansında konu açılır, tasarım önerileri gelir giderdi. 1.18, Mart 2022’de çıkıyor ve bu tartışmayı somut bir cevaba kavuşturuyor. Sürüm henüz tam anlamıyla elimde değil — bu yazıyı sürüm öncesi notlar ve teknik öneri belgelerine (proposal) dayanarak yazıyorum — ama gelecek olanlar hakkında yeterince netlik var.

Generic’lerin Go’ya gelmesi yalnızca yeni bir sözdizimi eklemesi değil. Dilin temel tasarım ilkelerinden biriyle — sadelik ve açıklık — bir gerilim içinde olan bir özelliğin nasıl ustaca dengelendiğini gösteriyor.

Go neden bu kadar beklemek istedi

Go’nun generic’leri bekleten şeyi anlamak için dilin felsefesine bakmak gerekiyor. Rob Pike ve ekibi sadeliği baş ilke olarak seyretti. Generic’ler, çoğu dilde tür sistemi karmaşıklığını önemli ölçüde artıran bir özelliktir. C++ template’leri, Java ve C#‘ın generic implementasyonları hem derleme süresine hem de hata mesajlarının okunabilirliğine ciddi yük bindiriyor.

Go ekibinin asıl sorusu buydu: generic’leri ekleyelim ama dilin sadeliğini bozmayalım. Yıllarca süren tasarım çalışması bu dengeyi bulmaya harcandı. Sonuçta çıkan çözüm — type parameter’lar ve interface constraint’ler — bu dengeyi oldukça iyi tutuyor gibi görünüyor.

Type parameter sözdizimi

1.18’deki sözdizimi köşeli parantez kullanıyor. Bir örnek:

func Map[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

Bu fonksiyon herhangi bir dilim (slice) üzerinde çalışıyor. Daha önce bu tür bir soyutlama için ya interface{} kullanmak ya da her tür için ayrı fonksiyon yazmak gerekiyordu. İkisi de hoş değildi.

Constraint’ler için mevcut interface sistemi genişletildi:

type Number interface {
    int | int64 | float64
}

func Sum[T Number](s []T) T {
    var total T
    for _, v := range s {
        total += v
    }
    return total
}

Type union ile constraint tanımlamak, daha güvenli ve açık bir yapı sunuyor. “Bu fonksiyon int ya da float64 alır” ifadesi artık kod içinde açıkça yaşıyor.

Ne kazanıyoruz, ne kaybediyoruz

Kazanç tarafı belirgin: boilerplate kod azalıyor. Sıralama, filtreleme, dönüşüm gibi evrensel koleksiyon işlemleri artık tek bir tanımla yazılabiliyor. Standart kütüphane de zamanla bu yapıyı kullanmaya başlayacak.

Öte yandan, kaybetme riski taşıdığımız şeyler de var. Generic’ler kodun okunabilirliğini zorlaştırabilir — özellikle constraint’ler karmaşıklaştığında. Java dünyasında “generic cehennemi” diye anılan, iç içe type parameter’ların tip hatası mesajlarını çözülmez hale getirdiği durumlar yaşandı. Go’nun tasarımı bunu engelliyor mu? Kısmen. Ama gereğinden karmaşık constraint’ler yazmak mümkün.

Derleme süresi de tartışmalı. Başlangıçta performans kayıpları raporlandı; bu kısmen beklenen bir şey, zaman içinde iyileşecek.

Pratikte ne zaman kullanacağım

Generic’leri her yerde kullanmayı planlamıyorum. Go’nun güçlü yanlarından biri okunabilirlik; bir fonksiyona type parameter eklemek her zaman okunabilirliği artırmıyor. Koleksiyon yardımcı fonksiyonları, veri dönüşüm işlemleri, tip güvenli veri yapıları (stack, queue, set) — bunlar generic’in değer yarattığı yerler.

Ekip kodunda ise daha temkinli olacağım. Generic’lerin değerini görmek için ekibin kavramı özümsemesi gerekiyor; hemen her yere yaymak, “bu neden böyle?” sorusunu artırır.

Sürüm ellere geçtikçe topluluktan gerçek deneyimler gelecek. O noktada bu ilk izlenimleri revize etmek gerekecek. Şimdilik söyleyebildiğim şu: Go 1.18, dilin on yıllık tarihindeki en anlamlı değişikliği getiriyor ve bu değişikliğin nasıl ustaca yapıldığı, dil tasarımı açısından ilginç bir ders.

Etiketler: #Go
Paylaş:

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi