Herkese merhaba; Kendim go öğrenmeye çalışıyorum. Öğrenirken sizede göstermek istedim.
Generic türler, bir işlevin veya veri yapısının herhangi bir türle çalışabilmesini sağlayan özelleştirilebilir türlerdir. Bu, aynı kodun farklı veri türleri için yeniden kullanılabilir hale gelmesini sağlar.
Neden generic kullanmalıyız?
Generic, aynı işlemi farklı veri tipleri üzerinde uygulayabilen ve kod tekrarını azaltan bir yol sunar. Bu, aynı işlevi farklı veri tipleri için uyarlanabilir hale getirir ve böylece kodun daha geniş bir yelpazede kullanılmasına olanak tanır.
"Bir fonksiyonun generic olması" terimi, genellikle programlama dilleri ve yazılım geliştirme bağlamında kullanılır. Generic fonksiyonlar veya generic programlama, genel ve yeniden kullanılabilir kod oluşturmayı ve veri tiplerine bağımlı olmadan işlemleri gerçekleştirmeyi sağlayan bir programlama yaklaşımını ifade eder.
Generic fonksiyonlar, aynı işlemi farklı veri tipleri üzerinde uygulayabilen ve kod tekrarını azaltan bir yol sunar. Bu, aynı işlevi farklı veri tipleri için uyarlanabilir hale getirir ve böylece kodun daha geniş bir yelpazede kullanılmasına olanak tanır.
Örneğin, bir dilde bir dizi elemanı sıralamak için bir generic fonksiyon kullanabilirsiniz. Bu generic fonksiyon, sayılar, metin dizileri veya diğer veri tipleri gibi farklı veri tipleri üzerinde çalışabilir. Veri tipine bağımlı olmadığı için, aynı sıralama işlemi farklı veri tipleri için tekrar tekrar yazılmak zorunda kalmaz.
Go'da bir generic tür tanımlamak için type
anahtar kelimesini kullanırız ve Tıp belirtmek icin Koşeli parantezler '[]'
içinde Allias Tip
şeklinde belirtiriz. Örnek: [T int]
kısaca burada T
; int
tipine karşılık geliyor.
ornek: Aynı tipte hem value, hemde pointer tutan bir struct
miz olsun. Ama herhangi veri tipi ile calışssın
type Pval[T any] struct {
val T
ptr *T
}
func main() {
s := "Hello"
sp := Pval[string]{
val: s,
ptr: &s,
}
i := 10
si := Pval[int]{
val: i,
ptr: &i,
}
fmt.Println(sp)
fmt.Println(si)
}
gordugunuz gibi tek bir struct ile hem integer hemde string tipinde aynı sonuca ulaştım
<sub><sub><sub>Ornegin terminal cıktısı</sub></sub></sub>
{Hello 0xc000014070}
{10 0xc000012150}
Generic Fonksiyonlar Nedir?
Generic fonksiyonlar, farklı türler için işlevselliği genişletmek için kullanılır. Özellikle, bir işlevi bir türden diğerine taşımak için tekrar kod yazma ihtiyacını ortadan kaldırır.
örnek: Hem float hem integer tipinde sayıları çarpabilen bir fonksiyonumuz olsun.
func Carp[T int | float64](a, b T) T {
return a * b
}
func main() {
intA := 10
intB := 2
intSonuc := Carp(intA, intB)
fmt.Println(intSonuc)
flotA := 10.0
floatB := 7.42
floatSonuc := Carp(flotA, floatB)
fmt.Println(floatSonuc)
}
Ornekte gorulduğü gibi hem float hem integer tipinde verileri çarpabilen bir fonksiyon elde ettik
<sub><sub><sub>Ornegin terminal cıktısı</sub></sub></sub>
20
74.2
Örnek: Herhangi bir slice
türündeki veriyi orijinal veriyi bozmadan filtrelemek ve JavaScript
'tekine benzer bir işlem yaparak döndüren bir fonksiyon oluşturalım.
<sub><sub><sub>Javascript teki filter fonksiyonu su yapıda</sub></sub></sub>
function filter<T any>(prediction: (el T, index?: number, arr?: T[]) => boolean): T[]
bunu go ya uyarlarsak suna benzer birsey oluyor. Gordugunuz gibi syntax farkı dışında aslında hemen hemen aynı yapıdalar
<sub><sub><sub>Go daki yapı ise bu şekilde olacak</sub></sub></sub>
func Filter[T any](source *[]T, prediction func(item T, index int) bool)) []T
any
türü herhangi bir tipte veri olabilir. Aslında interface{}
tipi için tanımlanmış bir allias. source bizim kaynak slice
'ımız. Prediction ise slice
daki her bir elaman icin donecek fonksiyon ve bundandan gelecek true veya false değerine göre slice
ımızı filtreleyecek
<sub><sub><sub>Fonksiyonun implementasyonu</sub></sub></sub>
func filter[T interface{}](source []T, prediction func(item T, index int) bool) []T {
filtered := source[:0]
for in, it := range sl {
if prediction(it, in) {
filtered = append(filtered, it)
}
}
return filtered
}
artık bu fonksıyonu herhangı bi veri tipindeki sliceları filtrelemek icin kullanabiliriz.
type EvcilHayvan struct {
ID int
Name string
}
func main() {
h := []EvcilHayvan{
{
ID: 1,
Name: "Kömür",
},
{ ID: 2,
Name: "Boncuk",
},
{
ID: 3,
Name: "Fıstık",
},
{
ID: 4,
Name: "Boncuk",
},
}
boncuklar := Filter(h, func(hayvan EvcilHayvan, index int) bool {
return hayvan.Name == "Boncuk"
})
fmt.Println(boncuklar)
}
<sub><sub><sub>Terminal cıktısı şu şekilde</sub></sub></sub>
[{2 Boncuk} {4 Boncuk}]
Baska bir ornek: Bir elemanın slice içinde olup olmadıgını kontrol edip true veya false dönen bir fonksiyon yapalım
func IfIn[T comparable](slice []T, item T) bool
Comparable
tüm karşılaştırılabilir türler tarafından uygulanan bir arabirimdir (booleanlar, sayılar, dizeler, işaretçiler (pointer), kanallar (channel), karşılaştırılabilir türlerin dizileri, tüm alanları karşılaştırılabilir türlerden oluşan yapılar). Karşılaştırılabilir arabirimi, yalnızca bir tür parametresi olarak kullanılabilir, bir değişken türü olarak değil.
func IfIn[T comparable](slice []T, item T) bool {
for _, it := range slice {
if item == it {
return true
}
}
return false
}
Çoğu dildeki FindIndex metodlarına benziyor tek fark elemanın indexi veya -1 yerine true veya false donuyor.
Ornek kullanım: olusturdugumuz http handlerlarının kaydolup kaydolmadıgını test eden bir case'imiz olsun. Ancak kaydettigimiz her Get handleri icin birde HEAD methodu icin duplicate olusturdugunu varsayalim.
"/hello" yoluna bir get handler ekledigimizi dusunun, hem Get hemde Head istekleri icin ayni handleri stage ekliyor. Yani stacktaki handler sayisi uygulamadaki handler sayisini ile eslesmiyor
[
{
Method: "GET",
Path: "/hello/:name"
},
{
Method: "HEAD",
Path: "/hello/:name"
},
{
Method: "GET",
Path: "/hello"
},
{
Method: "HEAD",
Path: "/hello"
},
]
aslinda burda uygulamamizda sadece 2 handler var "/hello" ve "/hello/:name"
type Handler struct {
Method string
Path string
}
func TestHandlers(t testing.T) {
assert := assert.New(t)
// Yukardaki yapiyi ceken dummy fonksiyon
handlers := GetHandlers()
// asil olmasi gereken handlerlar
mockHandlers := []Handler{
{
Method: "GET",
Path: "/hello/:name"
},
{
Method: "GET",
Path: "/hello"
},
}
filteredHandlers := Filter(handlers, func(handler Handler, index int) bool {
return handler.Method != "HEAD" && IfIn(mockHandlers, Handler)
})
assert.Equal(filteredHandlers, mockHandlers)
}
eger mockHandlers taki hadlerlar stackin icinde mevcutsa testi gececektir yoksa fail verecektir
Kaynak
https://go.dev/doc/tutorial/generics
https://stackoverflow.com/questions/68166558/generic-structs-with-go
https://stackoverflow.com/questions/15323767/does-go-have-if-x-in-construct-similar-to-python