Skip to content

Jenerik

Jenerik veya daha akademik adı ile Parametreli Polimorfizm (Parameterized Polymorphism), kodun yeniden kullanımı ve esnekliği için tür parametreleme yoluyla gerçekleştirilir. Birçok programlama dilinde, parametreli polimorfizm önemli bir kavramdır. Fonksiyon veya veri yapılarının her tür için ayrı kod yazmadan farklı türlerde veri işleyebilmesini sağlar. İlk Go'da jenerik diye bir şey yoktu. Ancak doğuşundan beri, topluluğun Go için en yüksek sesli talebi jenerik eklenmesiydi. Sonunda Go dili 2022'de 1.18 sürümünde jenerik desteği ekledi.

Tasarım

Go dili başlangıçta jenerik tasarlarken, aşağıdaki planları düşündü

  • stenciling: Monomorfizasyon, tipik olarak C++, Rust gibi dillerde görülür. Kullanılan her tür için bir şablon kodu oluşturur. Bu en iyi performansa sahiptir, çalışma zamanı maliyeti tamamen yoktur. Performans doğrudan çağırmaya eşittir. Dezavantajı derleme hızını büyük ölçüde yavaşlatmasıdır (Go'nun kendisine kıyasla). Aynı zamanda her tür için kod oluşturulduğundan, derlenen ikili dosya boyutunun şişmesine neden olur.
  • dictionaries: Sadece bir kod kümesi oluşturur. Aynı zamanda derleme zamanında tüm kullanılacak tür bilgilerini depolayan bir tür sözlüğü salt okunur veri bölümünde oluşturur. Fonksiyon çağrılırken, sözlüğe göre tür bilgilerini sorgular. Bu yöntem derleme hızını yavaşlatmaz, boyut şişmesine neden olmaz. Ancak büyük çalışma zamanı maliyetine neden olur, jenerik performansı çok düşüktür.

Yukarıdaki iki yöntem iki aşırı uç temsil eder. Go dilinin nihayetinde seçtiği uygulama planı Gcshape stenciling'dir. Bu bir uzlaşma seçimidir. Aynı bellek şekline sahip türler için (şekil bellek ayırıcı tarafından belirlenir) monomorfizasyon kullanılır. Onlar için aynı kodu oluşturur. Örneğin type Int int ile int aslında aynı türdür, bu nedenle bir kod kümesini paylaşır. Ancak işaretçiler için, tüm işaretçi türleri aynı bellek şekline sahip olsa da, örneğin *int, *Person aynı bellek şeklidir, ancak bir kod kümesini paylaşamazlar. Çünkü referans kaldırma sırasında hedef tür bellek düzeni tamamen farklıdır. Bu nedenle, Go aynı zamanda çalışma zamanında tür bilgilerini almak için sözlük kullanır. Bu nedenle Go'nun jeneriği de çalışma zamanı maliyetine sahiptir.

Giriş

Önce basit bir örneğe bakalım.

go
func Sum(a, b int) int {
   return a + b
}

Bu çok basit bir fonksiyondur. İki int türü tamsayıyı toplar ve sonucu döndürür. Eğer iki float64 türü kayan nokta sayısı toplamak istenirse, açıkçası bu mümkün değildir. Çünkü tür eşleşmez. Bir çözüm başka bir fonksiyon tanımlamaktır. Aşağıdaki gibi

go
func SumFloat64(a, b float64) float64 {
  return a + b
}

Sorun şu ki, bir matematik araç paketi geliştirilirse, tüm sayı türleri için iki sayı toplamı hesaplanırsa, her tür için bir fonksiyon mu yazılmalı? Açıkçası bu mümkün değil. Veya any türü + yansıma kullanılarak判断 edilebilir. Aşağıdaki gibi

go
func SumAny(a, b any) (any, error) {
  tA, tB := reflect.ValueOf(a), reflect.ValueOf(b)
  if tA.Kind() != tB.Kind() {
    return nil, errors.New("disMatch type")
  }

  switch tA.Kind() {
  case reflect.Int:
  case reflect.Int32:
    ...
  }
}

Ancak bu şekilde yazmak zahmetli ve performans düşüktür. Ancak Sum fonksiyonunun mantığı tamamen aynıdır, sadece iki sayıyı toplamak而已. İşte bu sırada jenerik kullanılması gerekir. Bu nedenle neden jeneriğe ihtiyaç vardır, jenerik tür ile ilgisiz yürütme mantığını çözmek içindir. Bu tür sorunlar verilen türün ne olduğuyla ilgilenmez, sadece karşılık gelen işlemi tamamlaması yeterlidir.

Sözdizimi

Jenerik yazımı şu şekildedir

go
func Sum[T int | float64](a, b T) T {
   return a + b
}

Tür Parametresi: T bir tür parametresidir. Somut parametre ne türde olursa olsun, iletilen türe bağlıdır

Tür Kısıtlaması: int | float64 bir tür kısıtlaması oluşturur. Bu tür kısıtlaması hangi türlerin izin verildiğini belirtir. Tür parametresinin tür aralığını kısıtlar

Tür Argümanı: Sum[int](1,2), manuel olarak int türü belirtilir, int tür argümanıdır.

İlk kullanım, açıkça hangi türün kullanılacağını belirtir. Aşağıdaki gibi

go
Sum[int](2012, 2022)

İkinci kullanım, tür belirtilmez, derleyicinin kendisinin çıkarmasına izin verilir. Aşağıdaki gibi

go
Sum(3.1415926, 1.114514)

Bu bir jenerik slice'dır. Tür kısıtlaması int | int32 | int64'dır

go
type GenericSlice[T int | int32 | int64] []T

Burada kullanım sırasında tür argümanı atlanamaz

go
GenericSlice[int]{1, 2, 3}

Bu bir jenerik hash tablosudur. Anahtar türü karşılaştırılabilir olmalıdır. Bu nedenle comparable arayüzü kullanılır. Değer tür kısıtlaması V int | string | byte'dır

go
type GenericMap[K comparable, V int | string | byte] map[K]V

Kullanım

go
gmap1 := GenericMap[int, string]{1: "hello world"}
gmap2 := make(GenericMap[string, byte], 0)

Bu bir jenerik yapıdır. Tür kısıtlaması T int | string'dir

go
type GenericStruct[T int | string] struct {
   Name string
   Id   T
}

Kullanım

go
GenericStruct[int]{
   Name: "jack",
   Id:   1024,
}
GenericStruct[string]{
   Name: "Mike",
   Id:   "1024",
}

Bu bir jenerik slice parametresi örneğidir

go
type Company[T int | string, S []T] struct {
   Name  string
   Id    T
   Stuff S
}

//Aşağıdaki gibi de olabilir
type Company[T int | string, S []int | []string] struct {
  Name  string
  Id    T
  Stuff S
}

Kullanım

go
Company[int, []int]{
   Name:  "lili",
   Id:    1,
   Stuff: []int{1},
}

::: ipucu

Jenerik yapıda, bu yazım daha çok önerilir

go
type Company[T int | string, S int | string] struct {
  Name  string
  Id    T
  Stuff []S
}

:::

SayAble bir jenerik arayüzdür. Person bu arayüzü uygular.

go
type SayAble[T int | string] interface {
   Say() T
}

type Person[T int | string] struct {
   msg T
}

func (p Person[T]) Say() T {
   return p.msg
}

func main() {
  var s SayAble[string]
  s = Person[string]{"hello world"}
  fmt.Println(s.Say())
}

Jenerik Arayüz

Jenerik arayüz daha iyi soyut kısıtlama yeteneği sağlayabilir. Aşağıda bir örnek var

go
func PrintObj[T fmt.Stringer](s T) {
	fmt.Println(s.String())
}

type Person struct {
	Name string
}

func (p Person) String() string {
	return fmt.Sprintf("Person: %s", p.Name)
}

func main() {
	PrintObj(Person{Name: "Alice"})
}

Jenerik olmayan arayüzü jeneriğin tür parametresi olarak da kullanabilirsiniz

go
func Write[W io.Writer](w W, bs []byte) (int, error) {
	return w.Write(bs)
}

Jenerik Tür Assertion

any türü üzerinde tür assertion yapmak için jenerik kullanabiliriz. Aşağıdaki fonksiyon tüm türleri assertion yapabilir.

go
func Assert[T any](v any) (bool, T) {
	var av T
	if v == nil {
		return false, av
	}
	av, ok := v.(T)
	return ok, av
}

Tür Kümesi

1.18'den sonra, arayüz tanımı tür kümesi (type set) olarak değiştirildi. Tür kümesi içeren arayüzlere General interfaces yani genel arayüzler denir.

An interface type defines a type set

Tür kümesi sadece jenerikteki tür kısıtlaması için kullanılabilir. Tür bildirimi, tür dönüşümü, tür assertion için kullanılamaz. Tür kümesi bir küme olarak, boş küme, birleşim, kesişim vardır. Sonraki bölümlerde bu üç durum anlatılacaktır.

Birleşim

Arayüz türü SignedInt bir tür kümesidir. İşaretli tamsayı türlerinin birleşimi SignedInt'dir. Tersine SignedInt onların üst kümesidir.

go
type SignedInt interface {
   int8 | int16 | int | int32 | int64
}

Temel veri türleri böyle, diğer genel arayüzler için de aynıdır

go
type SignedInt interface {
  int8 | int16 | int | int32 | int64
}

type UnSignedInt interface {
  uint8 | uint16 | uint32 | uint64
}

type Integer interface {
  SignedInt | UnSignedInt
}

Kesişim

Boş olmayan arayüzün tür kümesi tüm elemanlarının tür kümelerinin kesişimidir. İnsan diline çevirirsek: Eğer bir arayüz birden fazla boş olmayan tür kümesi içeriyorsa, o zaman bu arayüz bu tür kümelerinin kesişimidir. Örnek aşağıdaki gibidir

go
type SignedInt interface {
   int8 | int16 | int | int32 | int64
}

type Integer interface {
   int8 | int16 | int | int32 | int64 | uint8 | uint16 | uint | uint32 | uint64
}

type Number interface {
  SignedInt
  Integer
}

Örnekteki kesişim kesinlikle SignedInt'tir.

go
func Do[T Number](n T) T {
   return n
}

Do[int](2)
DO[uint](2) // Derleme başarısız

Boş Küme

Boş küme kesişim olmamasıdır. Örnek aşağıdaki gibidir. Aşağıdaki örnekteki Integer bir tür boş kümesidir.

go
type SignedInt interface {
  int8 | int16 | int | int32 | int64
}

type UnsignedInt interface {
  uint8 | uint16 | uint | uint32 | uint64
}

type Integer interface {
  SignedInt
  UnsignedInt
}

Çünkü işaretsiz tamsayı ve işaretli tamsayı iki kümenin kesinlikle kesişimi yoktur. Bu nedenle kesişim boş kümedir. Aşağıdaki örnekte hangi tür iletilirse iletilsin derleme başarısız olur.

go
Do[Integer](1)
Do[Integer](-100)

Boş Arayüz

Boş arayüz boş küme ile aynı değildir. Boş arayüz tüm tür kümelerinin kümesidir. Yani tüm türleri içerir.

go
func Do[T interface{}](n T) T {
   return n
}

func main() {
   Do[struct{}](struct{}{})
   Do[any]("abc")
}

Ancak genellikle any jenerik parametresi olarak kullanırız. Çünkü interface{} güzel görünmüyor.

Alt Tür

type anahtar kelimesi kullanılarak yeni bir tür bildirildiğinde, alt türü tür kümesinde olsa bile, iletilirken hala derleme başarısız olur.

go
type Int interface {
   int8 | int16 | int | int32 | int64 | uint8 | uint16 | uint | uint32 | uint64
}

type TinyInt int8

func Do[T Int](n T) T {
   return n
}

func main() {
   Do[TinyInt](1) // Derleme başarısız, alt türü Int tür kümesi aralığında olsa bile
}

İki çözüm vardır. Birincisi tür kümesine bu türü birleştirmektir. Ancak bu anlamsızdır. Çünkü TinyInt ile int8 alt türü tutarlıdır. Bu nedenle ikinci çözüm vardır.

go
type Int interface {
   int8 | int16 | int | int32 | int64 | uint8 | uint16 | uint | uint32 | uint64 | TinyInt
}

~ sembolü kullanılarak alt tür belirtilir. Eğer bir türün alt türü bu tür kümesine aitse, o zaman bu tür bu tür kümesine aittir. Aşağıda gösterildiği gibi

go
type Int interface {
   ~int8 | ~int16 | ~int | ~int32 | ~int64 | ~uint8 | ~uint16 | ~uint | ~uint32 | ~uint64
}

Değiştirildikten sonra derleme başarılı olur.

go
func main() {
   Do[TinyInt](1) // Derleme başarılı, çünkü TinyInt Int tür kümesinde
}

Dikkat Noktaları

Jenerik bir türün temel türü olarak kullanılamaz

Aşağıdaki yazım yanlıştır. Jenerik parametresi T temel tür olarak kullanılamaz

go
type GenericType[T int | int32 | int64] T

Aşağıdaki yazım izin verilse de, anlamsızdır ve sayı taşması sorununa neden olabilir. Bu nedenle önerilmez

go
type GenericType[T int | int32 | int64] int

Jenerik tür tür assertion kullanamaz

Jenerik tür üzerinde tür assertion kullanmak derleme başarısız olur. Jeneriğin çözmek istediği sorun tür ile ilgisiz'dir. Eğer bir sorun farklı türlere göre farklı mantık yapmayı gerektiriyorsa, o zaman kesinlikle jenerik kullanılmamalıdır. interface{} veya any kullanılmalıdır.

go
func Sum[T int | float64](a, b T) T {
   ints,ok := a.(int) // İzin verilmez
   switch a.(type) { // İzin verilmez
   case int:
   case bool:
      ...
   }
   return a + b
}

Anonim yapı jeneriği desteklemez

Anonim yapılar jeneriği desteklemez. Aşağıdaki kod derleme başarısız olur

go
testStruct := struct[T int | string] {
   Name string
   Id T
}[int]{
   Name: "jack",
   Id: 1
}

Anonim fonksiyon özel jeneriği desteklemez

Aşağıdaki iki yazım da derleme başarısız olur

go
var sum[T int | string] func (a, b T) T
sum := func[T int | string](a,b T) T{
    ...
}

Ancak mevcut jenerik türü kullanabilir. Örneğin kapamada

go
func Sum[T int | float64](a, b T) T {
  sub := func(c, d T) T {
    return c - d
  }
  return sub(a,b) + a + b
}

Jenerik yöntem desteklemez

Yöntemler jenerik parametreye sahip olamaz. Ancak receiver jenerik parametreye sahip olabilir. Aşağıdaki kod derleme başarısız olur

go
type GenericStruct[T int | string] struct {
   Name string
   Id   T
}

func (g GenericStruct[T]) name[S int | float64](a S) S {
   return a
}

Tür kümesi tür argümanı olarak kullanılamaz

Tür kümesi içeren herhangi bir arayüz, tür argümanı olarak kullanılamaz.

go
type SignedInt interface {
  int8 | int16 | int | int32 | int64
}

func Do[T SignedInt](n T) T {
   return n
}

func main() {
   Do[SignedInt](1) // Derleme başarısız
}

Tür kümesinin kesişim sorunu

Arayüz olmayan türler için, tür birleşiminde kesişim olamaz. Aşağıdaki örnekte TinyInt ile ~int8 kesişimi vardır.

go
type Int interface {
   ~int8 | ~int16 | ~int | ~int32 | ~int64 | ~uint8 | ~uint16 | ~uint | ~uint32 | ~uint64 | TinyInt // Derleme başarısız
}

type TinyInt int8

Ancak arayüz türü için, kesişime izin verilir. Aşağıdaki örnek gibi

go
type Int interface {
   ~int8 | ~int16 | ~int | ~int32 | ~int64 | ~uint8 | ~uint16 | ~uint | ~uint32 | ~uint64 | TinyInt // Derleme başarılı
}

type TinyInt interface {
  int8
}

Tür kümesi doğrudan veya dolaylı olarak kendini birleştiremez

Aşağıdaki örnekte, Floats doğrudan kendini birleştirir. Double da Floats'ı birleştirir. Bu nedenle dolaylı olarak kendini birleştirir.

go
type Floats interface {  // Kod derleme başarısız
   Floats | Double
}

type Double interface {
   Floats
}

comparable arayüzü tür kümesine birleştirilemez

Aynı şekilde, tür kısıtlamasına da birleştirilemez. Bu nedenle temelde ayrı kullanılır.

go
func Do[T comparable | Integer](n T) T { // Derleme başarısız
   return n
}

type Number interface { // Derleme başarısız
  Integer | comparable
}

type Comparable interface { // Derleme başarılı ancak anlamsız
  comparable
}

Yöntem kümesi tür kümesine birleştirilemez

Yöntem içeren herhangi bir arayüz, tür kümesine birleştirilemez

go
type I interface {
    int | fmt.Stringer // cannot use fmt.Stringer in union (fmt.Stringer contains methods)
}

Ancak kesişim yapabilirler. Ancak bunu yapmak anlamsızdır

go
type I interface {
    int
    fmt.Stringer
}

Kullanım

Veri yapısı jeneriğin en yaygın kullanım senaryosudur. Aşağıda iki veri yapısı kullanarak jeneriğin nasıl kullanılacağını göstereceğiz.

Kuyruk

Aşağıda jenerik kullanarak basit bir kuyruk uygulaması. Önce kuyruk türünü bildiriyoruz. Kuyruk eleman türü herhangi bir tür olabilir. Bu nedenle tür kısıtlaması any'dir

go
type Queue[T any] []T

Toplamda dört yöntem var: Pop, Peek, Push, Size. Kod aşağıdaki gibidir.

go
type Queue[T any] []T

func (q *Queue[T]) Push(e T) {
  *q = append(*q, e)
}

func (q *Queue[T]) Pop(e T) (_ T) {
  if q.Size() > 0 {
    res := q.Peek()
    *q = (*q)[1:]
    return res
  }
  return
}

func (q *Queue[T]) Peek() (_ T) {
  if q.Size() > 0 {
    return (*q)[0]
  }
  return
}

func (q *Queue[T]) Size() int {
  return len(*q)
}

Pop ve Peek yöntemlerinde, dönüş değerinin _ T olduğunu görebilirsiniz. Bu isimli dönüş değerinin kullanımıdır. Ancak alt çizgi _ kullanılarak anonim olduğu belirtilir. Bu gereksiz değildir, jenerik sıfır değerini belirtmek içindir. Jenerik kullanıldığından, kuyruk boş olduğunda, sıfır değer döndürmek gerekir. Ancak tür bilinmediğinden, somut tür döndürülemez. Yukarıdaki yöntemle jenerik sıfır değeri döndürülebilir. Jenerik değişken bildirerek sıfır değer sorununu çözebilirsiniz. Bir jenerik değişken için, varsayılan değeri bu türün sıfır değeridir. Aşağıdaki gibi

go
func (q *Queue[T]) Pop(e T) T {
    var res T
  if q.Size() > 0 {
    res = q.Peek()
    *q = (*q)[1:]
    return res
  }
  return res
}

Yığın (Heap)

Yukarıdaki kuyruk örneğinde, elemanlar üzerinde hiçbir gereksinim olmadığı için tür kısıtlaması any'dir. Ancak yığın farklıdır. Yığın özel bir veri yapısıdır. O(1)时间内 maksimum veya minimum değeri判断 edebilir. Bu nedenle elemanlar üzerinde bir gereksinim vardır: Sıralanabilir tür olmalıdır. Ancak yerleşik sıralanabilir türler sadece sayılar ve string'lerdir. Bu nedenle yığın başlatılırken, özel bir karşılaştırıcı iletilmelidir. Karşılaştırıcı çağrılan tarafından sağlanır ve karşılaştırıcı da jenerik kullanmalıdır. Aşağıdaki gibi

go
type Comparator[T any] func(a, b T) int

Aşağıda basit bir ikili yığın uygulaması var. Önce jenerik yapıyı bildiriyoruz. Hala any kullanarak kısıtlıyoruz. Bu şekilde herhangi bir türü depolayabiliriz

go
type Comparator[T any] func(a, b T) int

type BinaryHeap[T any] struct {
  s []T
  c Comparator[T]
}

Birkaç yöntem uygulaması

go
func (heap *BinaryHeap[T]) Peek() (_ T) {
  if heap.Size() > 0 {
    return heap.s[0]
  }
  return
}

func (heap *BinaryHeap[T]) Pop() (_ T) {
  size := heap.Size()
  if size > 0 {
    res := heap.s[0]
    heap.s[0], heap.s[size-1] = heap.s[size-1], heap.s[0]
    heap.s = heap.s[:size-1]
    heap.down(0)
    return res
  }
  return
}

func (heap *BinaryHeap[T]) Push(e T) {
  heap.s = append(heap.s, e)
  heap.up(heap.Size() - 1)
}

func (heap *BinaryHeap[T]) up(i int) {
  if heap.Size() == 0 || i < 0 || i >= heap.Size() {
    return
  }
  for parentIndex := i>>1 - 1; parentIndex >= 0; parentIndex = i>>1 - 1 {
    // Büyük veya eşit
    if heap.compare(heap.s[i], heap.s[parentIndex]) >= 0 {
      break
    }
    heap.s[i], heap.s[parentIndex] = heap.s[parentIndex], heap.s[i]
    i = parentIndex
  }
}

func (heap *BinaryHeap[T]) down(i int) {
  if heap.Size() == 0 || i < 0 || i >= heap.Size() {
    return
  }
  size := heap.Size()
  for lsonIndex := i<<1 + 1; lsonIndex < size; lsonIndex = i<<1 + 1 {
    rsonIndex := lsonIndex + 1

    if rsonIndex < size && heap.compare(heap.s[rsonIndex], heap.s[lsonIndex]) < 0 {
      lsonIndex = rsonIndex
    }

    // Küçük veya eşit
    if heap.compare(heap.s[i], heap.s[lsonIndex]) <= 0 {
      break
    }
    heap.s[i], heap.s[lsonIndex] = heap.s[lsonIndex], heap.s[i]
    i = lsonIndex
  }
}

func (heap *BinaryHeap[T]) Size() int {
  return len(heap.s)
}

func NewHeap[T any](n int, c Comparator[T]) BinaryHeap[T] {
	var heap BinaryHeap[T]
	heap.s = make([]T, 0, n)
	heap.Comparator = c
	return heap
}

Kullanım aşağıdaki gibidir

go
type Person struct {
  Age  int
  Name string
}

func main() {
  heap := NewHeap[Person](10, func(a, b Person) int {
    return cmp.Compare(a.Age, b.Age)
  })
  heap.Push(Person{Age: 10, Name: "John"})
  heap.Push(Person{Age: 18, Name: "mike"})
  heap.Push(Person{Age: 9, Name: "lili"})
  heap.Push(Person{Age: 32, Name: "miki"})
  fmt.Println(heap.Peek())
  fmt.Println(heap.Pop())
  fmt.Println(heap.Peek())
}

Çıktı

{9 lili}
{9 lili}
{10 John}

Jenerik desteği ile, orijinal olarak sıralanamayan türler karşılaştırıcı iletildikten sonra yığın kullanabilir. Bu kesinlikle önce interface{} kullanarak tür dönüşümü ve assertion yapmaktan daha zarif ve rahattır.

Nesne Havuzu

Orijinal nesne havuzu sadece any türü kullanabilir. Her seferinde alındığında tür assertion yapılmalıdır. Jenerik ile basitçe dönüştürüldükten sonra, bu işten kurtulunabilir.

go
package main

import (
	"bytes"
	"fmt"
	"sync"
)

func NewPool[T any](newFn func() T) *Pool[T] {
	return &Pool[T]{
		pool: &sync.Pool{
			New: func() interface{} {
				return newFn()
			},
		},
	}
}

type Pool[T any] struct {
	pool *sync.Pool
}

func (p *Pool[T]) Put(v T) {
	p.pool.Put(v)
}

func (p *Pool[T]) Get() T {
	var v T
	get := p.pool.Get()
	if get != nil {
		v, _ = get.(T)
	}
	return v
}

func main() {
	bufferPool := NewPool(func() *bytes.Buffer {
		return bytes.NewBuffer(nil)
	})

	for range 100 {
		buffer := bufferPool.Get()
		buffer.WriteString("Hello, World!")
		fmt.Println(buffer.String())
		buffer.Reset()
		bufferPool.Put(buffer)
	}
}

Özet

Go'nun büyük özelliklerinden biri derleme hızının çok hızlı olmasıdır. Derleme hızlıdır çünkü derleme zamanında yapılan optimizasyonlar azdır. Jenerik eklenmesi derleyicinin iş yükünü artırır, iş daha karmaşık hale gelir. Bu kaçınılmaz olarak derleme hızının yavaşlamasına neden olur. Aslında go1.18 jenerik çıkardığında gerçekten derlemeyi daha yavaş yaptı. Go ekibi hem jenerik eklemek istiyor hem de derleme hızını çok yavaşlatmak istemiyor. Geliştirici rahat kullanırsa, derleyici zorlanır. Tersine derleyici rahatlar (en rahatsız edici tabii ki jenerik olmaması), geliştirici zorlanır. Şimdiki jenerik bu iki taraf arasında uzlaşma ürünüdür.

Golang by www.golangdev.cn edit