Go Programming

Go Jenerikler İçin Hızlı Başvuru: Haritalar, Dilimler ve Özel Kısıtlamalar İçin 10 Pratik Kod Deseni

Go 1.18'in yayınlanmasından bu yana jenerikler dili dönüştürdü; interface{} veya yansıma (reflection) karmaşası olmadan, tür güvenli ve yeniden kullanılabilir kod yazmayı mümkün kıldı. Orta ve ileri düzey geliştiriciler için jeneriklere hakim olmak artık isteğe bağlı değil, özdeş, performanslı ve sürdürülebilir Go uygulamaları yazmak için şarttır.

Bu hızlı başvuru kılavuzu, en yaygın kullanım durumlarını kapsayan 10 temel deseni özetler: dilim manipülasyonu, harita işleme ve özel tür kısıtlamalarından yararlanma. İster bir veri işleme hattı ister karmaşık bir API işleyicisi geliştiriyor olun, bu desenler daha temiz Go kodu yazmanıza yardımcı olacaktır.

1. Kimlik Kısıtlaması: Temel Tür Parametreleri

En basit jenerik fonksiyon formu, `any` takma adını (ki bu `interface{}` ile eşdeğerdir) veya belirli bir tür kümesine kısıtlanmış bir tür parametresini kullanır. Bu, girdinin türüyle aynı türde bir değer döndürmeniz gerektiğinde idealdir.

func firstElement[T any](s []T) (T, bool) {
    var zero T
    if len(s) == 0 {
        return zero, false
    }
    return s[0], true
}

2. Matematiksel İşlemler İçin Sayısal Kısıtlamalar

Go'nun standart kütüphanesi, `~int` veya `~float64` gibi yerleşik kısıtlamalar sağlar. Tilda (~) işareti, alt türün tamsayı veya kayan nokta türüne uymasını gerektiğini belirtir; bu da özel tamsayı türleriyle çalışmanıza olanak tanır.

func SumNumbers[N int | float64](nums []N) N {
    var sum N
    for _, n := range nums {
        sum += n
    }
    return sum
}

3. Birleşim Türleri ile Özel Kısıtlamalar

Tür parametrelerini birden fazla farklı türe kısıtlamak için özel arayüzler tanımlayabilirsiniz. Bu, hem dizeleri hem de tamsayıları kabul etmesi gereken fonksiyonlar için güçlüdür.

type Number interface {
    int | int64 | float32 | float64
}

func Max[T Number](a, b T) T {
    if a > b {
        return a
    }
    return b
}

4. Jenerik Dilim Filtreleme

Filtreleme, dilimler üzerinde yapılan en yaygın işlemlerden biridir. Jenerik bir filtre fonksiyonu, boolean döndüren bir yordam fonksiyonunu kabul eder ve bu da son derece yeniden kullanılabilir mantık sağlar.

func Filter[T any](items []T, predicate func(T) bool) []T {
    var result []T
    for _, item := range items {
        if predicate(item) {
            result = append(result, item)
        }
    }
    return result
}

// Kullanım:
// evens := Filter([]int{1, 2, 3, 4}, func(n int) bool { return n%2 == 0 })

5. Jenerik Harita Birleştirme

Anahtar kısıtlamaları nedeniyle haritaları birleştirmek jeneriklerle zordur. Aynı anahtar ve değer türlerine sahip iki haritayı birleştiren jenerik bir fonksiyon oluşturabilirsiniz.

func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
    merged := make(map[K]V)
    for _, m := range maps {
        for k, v := range m {
            merged[k] = v
        }
    }
    return merged
}

6. Dilimlerde Benzersiz Değerler

Bir dilimde benzersizliği sağlamak için geçici bir küme olarak bir haritadan yararlanabilirsiniz. Bu desen, tür parametresi `T`'nin karşılaştırılabilir olmasını gerektirir.

func Distinct[T comparable](items []T) []T {
    seen := make(map[T]bool)
    var result []T
    for _, item := range items {
        if !seen[item] {
            seen[item] = true
            result = append(result, item)
        }
    }
    return result
}

7. Jenerik Yapı Haritalama (Nesne Haritalama)

Farklı yapı gösterimleri arasında dönüşüm yaparken, dönüşüm mantığını harici olarak tanımlamanız veya yardımcı kütüphaneler kullanmanız şartıyla, jenerikler yansıma olmadan alanları güvenli bir şekilde haritalamanıza olanak tanır.

func MapStructs[T, U any](items []T, fn func(T) U) []U {
    result := make([]U, len(items))
    for i, item := range items {
        result[i] = fn(item)
    }
    return result
}

8. Varsayılan Değerlerle Güvenli Harita Erişimi

Harita anahtarlarına erişim, sıfır değerlerinden kaçınmak için genellikle varlık kontrolü gerektirir. Jenerik bir fonksiyon, anahtar eksikse bir varsayılan değer döndürebilir.

func GetOrDefault[K comparable, V any](m map[K]V, key K, defaultVal V) V {
    if val, ok := m[key]; ok {
        return val
    }
    return defaultVal
}

9. Anahtara Göre Dilimleri Gruplama

Bir dilimin öğelerini bir anahtar fonksiyonuna göre kovalara gruplamak klasik bir fonksiyonel programlama desenidir. Bu, `K`'nın karşılaştırılabilir olmasını gerektirir.

func GroupBy[K comparable, V any](items []V, keyFunc func(V) K) map[K][]V {
    groups := make(map[K][]V)
    for _, item := range items {
        key := keyFunc(item)
        groups[key] = append(groups[key], item)
    }
    return groups
}

10. Kısıtlanmış İşaretçi Türleri

Bazen yapı işaretçileriyle çalışmanız gerekebilir. Tür parametrelerini arayüzler veya özel sözdizimi kullanarak işaretçilere kısıtlayabilirsiniz; ancak bu, değer türlerine göre daha az yaygındır.

type PointerToStruct interface {
    *MyStruct
}

// Not: Doğrudan işaretçi kısıtlamaları karmaşıktır; genellikle alt türü kısıtlarsınız
// ve ayrı fonksiyonlar veya dikkatli tasarım aracılığıyla hem işaretçileri hem de değerleri kabul edersiniz.

Sonuç

Go jenerikleri güçlü bir araçtır, ancak düşünceli bir uygulama gerektirir. Yukarıdaki desenler, tür güvenliğini korurken filtreleme, haritalama ve gruplama gibi yaygın senaryoların nasıl ele alınacağını gösterir. Jeneriklerin kodu karmaşıklaştırmak yerine basitleştirmesi gerektiğini unutmayın. Bu on desenden başlayın, özel kısıtlamalarla deneyim yapın ve Go projelerinizdeki faydalarını hızla göreceksiniz.

Share: