Skip to content

Yineleyici

Go'da belirli veri yapılarını yinelemek için kullanılan anahtar kelime for range'dir. Önceki bölümlerde bazı uygulamaları zaten tanıtılmıştı. Sadece dilin yerleşik birkaç veri yapısı üzerinde çalışabilir

  • Dizi
  • Slice
  • String
  • map
  • chan
  • Tamsayı değeri

Bu durumda kullanım çok esnek değildir, genişletilebilirlik yoktur, özel türler için neredeyse desteklenmez. Ancak go1.23 sürümü güncellendikten sonra, for range anahtar kelimesi range over func'u destekler. Bu şekilde özel yineleyiciler mümkün hale gelir.

Tanıma

Aşağıdaki örnekle yineleyiciyi ilk kez tanıyalım. Fonksiyon bölümünde anlatılan kapalı fonksiyonla Fibonacci dizisini çözen örneği hatırlıyor musunuz bilmiyorum. Uygulama kodu şu şekildedir

go
func Fibonacci(n int) func() (int, bool) {
  a, b, c := 1, 1, 2
  i := 0
  return func() (int, bool) {
    if i >= n {
      return 0, false
    } else if i < 2 {
      f := i
      i++
      return f, true
    }

    a, b = b, c
    c = a + b
    i++

    return a, true
  }
}

Bunu yineleyiciye dönüştürebiliriz. Aşağıdaki gibi gösterildiği gibi, kod miktarının biraz azaldığını görebilirsiniz

go
func Fibonacci(n int) func(yield func(int) bool) {
  a, b, c := 0, 1, 1
  return func(yield func(int) bool) {
    for range n {
      if !yield(a) {
        return
      }
      a, b = b, c
      c = a + b
    }
  }
}

Go'nun yineleyicisi range over func stilindedir. Doğrudan for range anahtar kelimesini kullanarak kullanabiliriz. Kullanımı eskisinden daha uygundur

go
func main() {
    n := 8
  for f := range Fibonacci(n) {
    fmt.Println(f)
  }
}

Çıktı şu şekildedir

0
1
1
2
3
5
8
13

Yukarıda gösterildiği gibi, yineleyici bir kapalı fonksiyondur. Parametre olarak bir geri çağırma fonksiyonu kabul eder. Hatta içinde yield gibi ifadeler görebilirsiniz. Python yazan kişiler buna aşinadır, Python'daki oluşturuculara benzer. Go'nun yineleyicisi herhangi bir yeni anahtar kelime veya sözdizimi özelliği eklemez. Yukarıdaki örnekte yield sadece bir geri çağırma fonksiyonudur, bir anahtar kelime değildir. Resmi bu adı anlamayı kolaylaştırmak için seçmiştir.

İtme Tarzı Yineleyici

Yineleyicinin tanımı hakkında, iter kütüphanesinde aşağıdaki açıklamayı bulabiliriz

An iterator is a function that passes successive elements of a sequence to a callback function, conventionally named yield.

Yineleyici, bir dizinin ardışık elemanlarını geleneksel olarak yield olarak adlandırılan bir geri çağırma fonksiyonuna ileten bir fonksiyondur.

Buradan kesin olarak öğrenebileceğimiz bir nokta, yineleyicinin bir fonksiyon olduğu ve parametre olarak bir geri çağırma fonksiyonu kabul ettiğidir. Yineleme sürecinde dizinin elemanlarını tek tek yield geri çağırma fonksiyonuna iletir. Önceki örnekte yineleyiciyi aşağıdaki şekilde kullanıyorduk

go
for f := range Fibonacci(n) {
    fmt.Println(f)
}

Resmi tanıma göre, yukarıdaki Backward yineleyici örneğinin kullanımı aşağıdaki kod ile eşdeğerdir

go
Fibonacci(n)(func(f int) bool {
    fmt.Println(f)
    return true
})

Döngü gövdesi yineleyicinin yield geri çağırma fonksiyonudur. Fonksiyon true döndürdüğünde yineleyici yinelemeye devam eder, aksi takdirde durur.

Ayrıca, iter standart kütüphanesi yineleyici türü iter.Seq'i tanımlar. Türü fonksiyondur.

go
type Seq[V any] func(yield func(V) bool)

type Seq2[K, V any] func(yield func(K, V) bool)

iter.Seq'ün geri çağırma fonksiyonu sadece bir parametre kabul eder. Yineleme sırasında for range'in sadece bir dönüş değeri vardır. Aşağıdaki gibi

go
for v := range iter {
  // body
}

iter.Seq2'nin geri çağırma fonksiyonu iki parametre kabul eder. Yineleme sırasında for range'in iki dönüş değeri vardır. Aşağıdaki gibi

go
for k, v := range iter {
  // body
}

Standart kütüphanede 0 parametreli Seq tanımlanmamış olsa da, bu tamamen izin verilir. Şuna eşdeğerdir

go
func(yield func() bool)

Kullanımı aşağıdaki gibi gösterilir

go
for range iter {
  // body
}

Geri çağırma fonksiyonunun parametre sayısı sadece 0 ila 2 olabilir. Fazlası derlenemez.

Kısacası, for range'deki döngü gövdesi yineleyicideki yield geri çağırma fonksiyonudur. for range kaç değer döndürürse, ilgili yield fonksiyonunun o kadar giriş parametresi vardır. Her yineleme turunda, yineleyici yield fonksiyonunu çağırır, yani döngü gövdesindeki kodu yürütür ve dizinin elemanlarını aktif olarak yield fonksiyonuna iletir. Elemanları aktif olarak ileten bu tür yineleyicilere genellikle itme tarzı yineleyici (pushing iterator) denir. Diğer dillerdeki foreach gibi tipik bir örnektir. Örneğin js

javascript
let arr = [1, 2, 3, 4, 5];
arr
  .filter((e) => e % 2 === 0)
  .forEach((e) => {
    console.log(e);
  });

Go'daki表现形式 range tarafından yineelenen elemanların döndürülmesidir.

go
for index, value := range iterator() {
  fmt.Println(index, value)
}

Bazı dillerde (Java gibi) başka bir adı vardır: veri akışı işleme.

Döngü gövdesindeki kod geri çağırma fonksiyonu olarak yineleyiciye iletildiğinden ve büyük olasılıkla bir kapalı fonksiyon olduğundan, Go'nun bir kapalı fonksiyonun defer, return, break, goto gibi anahtar kelimeleri kullanırken normal döngü gövdesi kod segmenti gibi davranmasını sağlaması gerekir. Aşağıdaki birkaç durumu düşünün.

Örneğin yineleyici döngüsünde return kullanıldığında, yield geri çağırma fonksiyonunda bu return nasıl işlenmelidir?

go
for index, value := range iterator() {
    if value > 10 {
        return
  }
  fmt.Println(index, value)
}

Geri çağırma fonksiyonunda doğrudan return yapılamaz. Bu sadece yinelemeyi durdurur, return etkisine ulaşamaz

go
iterator()(func(index int, value int) bool {
  if value > 10 {
    return false
  }
  fmt.Println(index, value)
})

Yineleyici döngüsünde defer kullanıldığında da aynı durum geçerlidir

go
for index, value := range iterator() {
    defer fmt.Println(index, value)
}

Geri çağırma fonksiyonunda doğrudan defer kullanılamaz. Çünkü bu şekilde geri çağırma fonksiyonu bittiğinde doğrudan ertelenmiş çağrı yapılır

go
iterator()(func(index int, value int) bool {
  defer fmt.Println(index, value)
})

Diğer birkaç anahtar kelime break, continue, goto da benzerdir. Neyse ki Go bu durumları bizim için halletmiştir. Sadece kullanmamız yeterlidir. Şimdilik bunlarla ilgilenmemize gerek yoktur. Eğer ilgileniyorsanız rangefunc/rewrite.go içindeki kaynak kodu kendiniz inceleyebilirsiniz.

Çekme Tarzı Yineleyici

İtme tarzı yineleyici (pushing iterator) yineleme mantığını yineleyicinin kontrol etmesi, kullanıcının pasif olarak elemanları almasıdır. Tam tersi, çekme tarzı yineleyici (pulling iterator) kullanıcının yineleme mantığını kontrol etmesi, dizinin elemanlarını aktif olarak almasıdır. Genel olarak, çekme tarzı yineleyiciler next(), stop() gibi belirli fonksiyonlara sahiptir. Yinelemenin başlatılması veya sonlandırılması için kullanılır. Bir kapalı fonksiyon veya yapı olabilir.

go
scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line, err := scanner.Text(), scanner.Err()
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(line)
}

Yukarıda gösterildiği gibi, Scanner Text() yöntemi ile dosyadaki bir sonraki metin satırını alır, Scan() yöntemi ile yinelemenin bitip bitmediğini gösterir. Bu da çekme tarzı yineleyicinin bir modudur. Scanner durumu kaydetmek için yapı kullanır. iter kütüphanesinde tanımlanan çekme tarzı yineleyici durumu kaydetmek için kapalı fonksiyon kullanır. iter.Pull veya iter.Pull2 fonksiyonu ile standart bir itme tarzı yineleyiciyi çekme tarzı yineleyiciye dönüştürebiliriz. iter.Pull ile iter.Pull2 arasındaki fark ikincisinin iki dönüş değeri olmasıdır. İmza şu şekildedir

go
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())

func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func())

Her ikisi de parametre olarak bir yineleyici kabul eder, ardından yinelemeyi devam ettirmek ve durdurmak için kullanılan iki fonksiyon next() ve stop() döndürür.

go
func next() (V, bool)

func stop()

next yineelenen elemanı ve geçerli değerin geçerli olup olmadığını gösteren bir boolean değer döndürür. Yineleme bittiğinde next fonksiyonu elemanın sıfır değerini ve false döndürür. stop fonksiyonu yineleme sürecini sonlandırır. Çağrıcı yineleyiciyi artık kullanmadığında, yinelemeyi sonlandırmak için stop fonksiyonunu kullanmalıdır. Bu arada, birden fazla goroutine'in aynı yineleyicinin next fonksiyonunu çağırması yanlış bir uygulamadır. Çünkü eşzamanlı güvenli değildir.

Aşağıdaki örnekle gösterelim. İşlevi önceki Fibonacci yineleyicisini çekme tarzı yineleyiciye dönüştürmektir. Aşağıdaki gibi

go
func main() {
  n := 10
  next, stop := iter.Pull(Fibonacci(n))
  defer stop()
  for {
    fibn, ok := next()
    if !ok {
      break
    }
    fmt.Println(fibn)
  }
}

Çıktı

0
1
1
2
3
5
8
13
21
34

Bu şekilde yineleme mantığını next ve stop fonksiyonları ile manuel olarak kontrol edebiliriz. Belki bunu yapmanın gereksiz olduğunu düşünebilirsiniz. Böyle yapmak istiyorsanız neden en baştaki kapalı fonksiyon sürümünü doğrudan kullanmıyorsunuz? Aynı şekilde yinelemeyi kendiniz kontrol edebilirsiniz. Kapalı fonksiyonun kullanımı şu şekildedir

go
func main() {
  fib := Fibonacci(10)
    for {
        n, ok := fib()
        if !ok {
            break
        }
        fmt.Prinlnt(n)
    }
}

Dönüşüm süreci: kapalı fonksiyon → yineleyici → çekme tarzı yineleyici. Kapalı fonksiyon ile çekme tarzı yineleyicinin kullanımı neredeyse aynıdır, düşünce tarzları aynıdır. İkincisi çeşitli işlemler nedeniyle performans kaybına neden olabilir. Dürüst olmak gerekirse bu gerçekten gereksizdir. Uygulama senaryosu gerçekten çok fazla değildir. Ancak iter.pull, iter.Seq için vardır. Yani itme tarzı yineleyiciyi çekme tarzı yineleyiciye dönüştürmek için vardır. Sadece bir çekme tarzı yineleyici istiyorsanız, özel olarak bunun için bir itme tarzı yineleyici uygulamayı düşünün. Dönüşüm için karmaşıklığı ve performansı gözden geçirin. Tıpkı bu Fibonacci dizisi örneğinde olduğu gibi, bir tur dönüp tekrar başa dönüyorsunuz. Tek avantajı resmi yineleyici standardına uygun olması olabilir.

Hata İşleme

Yineleme sırasında hata oluşursa ne olur? Bunu yield fonksiyonuna ileterek for range'in döndürmesini sağlayabiliriz. Çağrıcının işlemesini sağlayabiliriz. Tıpkı bu satır yineleyici örneğinde olduğu gibi

go
func ScanLines(reader io.Reader) iter.Seq2[string, error] {
  scanner := bufio.NewScanner(reader)
  return func(yield func(string, error) bool) {
    for scanner.Scan() {
      if !yield(scanner.Text(), scanner.Err()) {
        return
      }
    }
  }
}

::: ipucu

Dikkat edilmesi gereken, ScanLines yineleyicisinin tek kullanımlık olduğu, dosya kapatıldıktan sonra tekrar kullanılamayacağıdır.

:::

İkinci dönüş değerinin error türü olduğunu görebilirsiniz. Kullanımı şu şekildedir

go
for line, err := range ScanLines(file) {
    if err != nil {
        fmt.Println(err)
        break
    }
    fmt.Println(line)
}

Bu şekilde işlemek sıradan hata işleme ile aynıdır. Çekme tarzı yineleyici de aynıdır

go
next, stop := iter.Pull2(ScanLines(file))
defer stop()
for {
    line, err, ok := next()
    if err != nil {
        fmt.Println(err)
        break
    } else if !ok {
        break
    }
    fmt.Println(line)
}

Eğer panic oluşursa, her zamanki gibi recovery kullanın.

go
defer func() {
    if err := recover(); err != nil {
        fmt.Println("panic:", err)
        os.Exit(1)
    }
}()

for line, err := range ScanLines(file) {
    if err != nil {
        fmt.Println(err)
        break
    }
    fmt.Println(line)
}

Çekme tarzı yineleyici hala aynıdır, burada göstermeyeceğiz.

Standart Kütüphane

Birçok standart kütüphane de yineleyiciyi destekler. En yaygın kullanılanlar slices ve maps standart kütüphaneleridir. Aşağıda birkaç pratik işlev tanıtılacaktır.

slices.All

go
func All[Slice ~[]E, E any](s Slice) iter.Seq2[int, E]

slices.All slice'ı bir slice yineleyicisine dönüştürür

go
func main() {
  s := []int{1, 2, 3, 4, 5}
  for i, n := range slices.All(s) {
    fmt.Println(i, n)
  }
}

Çıktı

0 1
1 2
2 3
3 4
4 5

slices.Values

go
func Values[Slice ~[]E, E any](s Slice) iter.Seq[E]

slices.Values slice'ı bir slice yineleyicisine dönüştürür, ancak indeksiz

go
func main() {
  s := []int{1, 2, 3, 4, 5}
  for n := range slices.Values(s) {
    fmt.Println(n)
  }
}

Çıktı

1
2
3
4
5

slices.Chunk

go
func Chunk[Slice ~[]E, E any](s Slice, n int) iter.Seq[Slice]

slices.Chunk fonksiyonu bir yineleyici döndürür. Bu yineleyici n elemanlı slice'ları çağrıcıya iletir

go
func main() {
  s := []int{1, 2, 3, 4, 5}
  for chunk := range slices.Chunk(s, 2) {
    fmt.Println(chunk)
  }
}

Çıktı

[1 2]
[3 4]
[5]

slices.Collect

func Collect[E any](seq iter.Seq[E]) []E

slices.Collect fonksiyonu slice yineleyicisini bir slice olarak toplar

go
func main() {
  s := []int{1, 2, 3, 4, 5}
  s2 := slices.Collect(slices.Values(s))
  fmt.Println(s2)
}

Çıktı

[1 2 3 4 5]

maps.Keys

go
func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K]

maps.Keys map'in tüm anahtarlarını yineleyen bir yineleyici döndürür. slices.Collect ile işbirliği yaparak doğrudan bir slice olarak toplanabilir.

go
func main() {
  m := map[string]int{"one": 1, "two": 2, "three": 3}
  keys := slices.Collect(maps.Keys(m))
  fmt.Println(keys)
}

Çıktı

[three one two]

Map sırasız olduğundan, çıktı da sabit değildir

maps.Values

go
func Values[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[V]

maps.Values map'in tüm değerlerini yineleyen bir yineleyici döndürür. slices.Collect ile işbirliği yaparak doğrudan bir slice olarak toplanabilir.

go
func main() {
  m := map[string]int{"one": 1, "two": 2, "three": 3}
  keys := slices.Collect(maps.Values(m))
  fmt.Println(keys)
}

Çıktı

[3 1 2]

Map sırasız olduğundan, çıktı da sabit değildir

maps.All

func All[Map ~map[K]V, K comparable, V any](m Map) iter.Seq2[K, V]

maps.All bir map'i bir map yineleyicisine dönüştürür

go
func main() {
  m := map[string]int{"one": 1, "two": 2, "three": 3}
  for k, v := range maps.All(m) {
    fmt.Println(k, v)
  }
}

Genellikle doğrudan bu şekilde kullanılmaz, diğer veri akışı işleme fonksiyonları ile birlikte kullanılır.

maps.Collect

go
func Collect[K comparable, V any](seq iter.Seq2[K, V]) map[K]V

maps.Collect bir map yineleyicisini bir map olarak toplayabilir

go
func main() {
  m := map[string]int{"one": 1, "two": 2, "three": 3}
  m2 := maps.Collect(maps.All(m))
  fmt.Println(m2)
}

collect fonksiyonu genellikle veri akışı işlemenin sonlandırma fonksiyonu olarak kullanılır.

Zincirleme Çağrı

Yukarıda standart kütüphane tarafından sağlanan fonksiyonlar aracılığıyla, bunları veri akışını işlemek için birleştirebiliriz. Örneğin veri akışını sıralamak gibi. Aşağıdaki gibi

go
sortedSlices := slices.Sorted(slices.Values(s))

Go'nun yineleyicisi kapalı fonksiyon kullanır. Sadece bu şekilde iç içe fonksiyon çağrıları yapılabilir. Kendisi zincirleme çağrı yapamaz. Çağrı zinciri uzadıktan sonra okunabilirlik çok zayıf olur. Ancak yapıyı kaydetmek için yineleyiciyi kendimiz uygulayabiliriz. Zincirleme çağrıyı gerçekleştirebiliriz.

demo

Basit bir zincirleme çağrı demosu aşağıda gösterilmiştir. Filter, Map, Find, Some gibi yaygın işlevleri içerir.

go
package iterx

import (
  "iter"
  "slices"
)

type SliceSeq[E any] struct {
  seq iter.Seq2[int, E]
}

func (s SliceSeq[E]) All() iter.Seq2[int, E] {
  return s.seq
}

func (s SliceSeq[E]) Filter(filter func(int, E) bool) SliceSeq[E] {
  return SliceSeq[E]{
    seq: func(yield func(int, E) bool) {
      // İndeksi yeniden düzenle
      i := 0
      for k, v := range s.seq {
        if filter(k, v) {
          if !yield(i, v) {
            return
          }
          i++
        }
      }
    },
  }
}

func (s SliceSeq[E]) Map(mapFn func(E) E) SliceSeq[E] {
  return SliceSeq[E]{
    seq: func(yield func(int, E) bool) {
      for k, v := range s.seq {
        if !yield(k, mapFn(v)) {
          return
        }
      }
    },
  }
}

func (s SliceSeq[E]) Fill(fill E) SliceSeq[E] {
  return SliceSeq[E]{
    seq: func(yield func(int, E) bool) {
      for i, _ := range s.seq {
        if !yield(i, fill) {
          return
        }
      }
    },
  }
}

func (s SliceSeq[E]) Find(equal func(int, E) bool) (_ E) {
  for i, v := range s.seq {
    if equal(i, v) {
      return v
    }
  }
  return
}

func (s SliceSeq[E]) Some(match func(int, E) bool) bool {
  for i, v := range s.seq {
    if match(i, v) {
      return true
    }
  }
  return false
}

func (s SliceSeq[E]) Every(match func(int, E) bool) bool {
  for i, v := range s.seq {
    if !match(i, v) {
      return false
    }
  }
  return true
}

func (s SliceSeq[E]) Collect() []E {
  var res []E
  for _, v := range s.seq {
    res = append(res, v)
  }
  return res
}

func (s SliceSeq[E]) Sort(cmp func(x, y E) int) []E {
  collect := s.Collect()
  slices.SortFunc(collect, cmp)
  return collect
}

func (s SliceSeq[E]) SortStable(cmp func(x, y E) int) []E {
  collect := s.Collect()
  slices.SortStableFunc(collect, cmp)
  return collect
}

func Slice[S ~[]E, E any](s S) SliceSeq[E] {
  return SliceSeq[E]{seq: slices.All(s)}
}

Sonra zincirleme çağrı ile işleyebiliriz. Birkaç kullanım örneğine bakalım.

Eleman değerini işle

go
func main() {
  s := []string{"apple", "banana", "cherry"}
  all := iterx.Slice(s).Map(strings.ToUpper).All()
  for i, v := range all {
    fmt.Printf("index: %d, value: %s\n", i, v)
  }
}

Çıktı

index: 0, value: APPLE
index: 1, value: BANANA
index: 2, value: CHERRY

Belirli bir değeri bul

go
func main() {
  s := []int{1, 2, 3, 4, 5}
  result := iterx.Slice(s).Find(func(i int, e int) bool {
    return e == 3
  })
  fmt.Println(result)
}

Çıktı

3

Slice'ı doldur

go
func main() {
  s := []int{1, 2, 3, 4, 5}
  result := iterx.Slice(s).Fill(6).Collect()
  fmt.Println(result)
}

Çıktı

[6 6 6 6 6]

Elemanları filtrele

go
func main() {
  s := []int{1, 2, 3, 4, 5}
  filter := iterx.Slice(s).Filter(func(i int, e int) bool {
    return e%2 == 0
  }).All()
  for i, v := range filter {
    fmt.Printf("Index: %d, Value: %d\n", i, v)
  }
}

Çıktı

Index: 0, Value: 2
Index: 1, Value: 4

Go'nun henüz kısaltılmış anonim fonksiyonları desteklemediğini belirtmek gerekir. js, rust, java'daki ok fonksiyonları gibi. Aksi takdirde zincirleme çağrı daha özlü ve zarif olabilir.

Performans

Go yineleyici için birçok işlem yaptığından, performansı kesinlikle yerel for range döngüsü kadar iyi değildir. En basit slice yinelemesinin performans farkını test edelim. Aşağıdaki birkaç türe ayrılır

  • Yerel for döngüsü
  • İtme tarzı yineleyici
  • Çekme tarzı yineleyici

Test kodu şu şekildedir. Test slice uzunluğu 1000'dir.

go
package main

import (
  "iter"
  "slices"
  "testing"
)

var s []int

const n = 10000

func init() {
  for i := range n {
    s = append(s, i)
  }
}

func testNaiveFor(s []int) {
  for i, n := range s {
    _ = i
    _ = n
  }
}

func testPushing(s []int) {
  for i, n := range slices.All(s) {
    _ = i
    _ = n
  }
}

func testPulling(s []int) {
  next, stop := iter.Pull2(slices.All(s))
  for {
    i, n, ok := next()
    if !ok {
      stop()
      return
    }
    _ = i
    _ = n
  }
}

func BenchmarkNaive_10000(b *testing.B) {
  for range b.N {
    testNaiveFor(s)
  }
}

func BenchmarkPushing_10000(b *testing.B) {
  for range b.N {
    testPushing(s)
  }
}

func BenchmarkPulling_10000(b *testing.B) {
  for range b.N {
    testPulling(s)
  }
}

Test sonucu şu şekildedir

goos: windows
goarch: amd64
pkg: golearn
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
BenchmarkNaive_10000
BenchmarkNaive_10000-16           492658              2398 ns/op               0 B/op          0 allocs/op
BenchmarkPushing_10000
BenchmarkPushing_10000-16         315889              3707 ns/op               0 B/op          0 allocs/op
BenchmarkPulling_10000
BenchmarkPulling_10000-16           2016            574509 ns/op             440 B/op         14 allocs/op
PASS
ok      golearn 4.029s

Sonuçlardan görebileceğimiz gibi, itme tarzı yineleyici ile yerel for range döngüsü arasında çok büyük bir fark yoktur. Ancak çekme tarzı yineleyici önceki ikisinden neredeyse iki kat daha yavaştır. Kullanırken kendi durumunuza göre düşünebilirsiniz.

Özet

Jenerik durumuna benzer şekilde, Go yineleyicisi de tartışmalıdır. Bazı kişilerin görüşüne göre yineleyici çok fazla karmaşıklık getirir, Go'nun sadelik felsefesine aykırıdır. Bu tür yineleyici kapalı fonksiyon kodu çoğaldıktan sonra, hata ayıklama zor olabilir, okuma daha da sinir bozucu olur.

Yineleyici hakkında şiddetli tartışmaları birçok yerde görebilirsiniz

Go yineleyicisine mantıklı bir şekilde bakalım. Gerçekten kod yazmayı daha uygun hale getirir. Özellikle slice türlerini işlerken. Ancak aynı zamanda biraz karmaşıklık getirir. Yineleyici bölümünün kod okunabilirliği azalır. Ancak genel olarak, bence bu gerçekten pratik bir özelliktir.

Golang by www.golangdev.cn edit