30 / 73

Generic funksiyalar

Har bir generic funksiya uchta asosiy qismdan tashkil topadi: bir yoki bir nechta tip parametrlarini e'lon qiluvchi type parameter ro'yxati, ushbu parametrlarga ruxsat etilgan amallarni belgilovchi constraint (cheklov) hamda chaqiruv vaqtida uzatiladigan (yoki avtomatik aniqlanadigan) type argument.

E'lon qilish va chaqirish

Type parameter ro'yxati funksiya nomi bilan parametrlar ro'yxati orasida kvadrat qavslarda yoziladi:

example.go
func identity[T any](value T) T {
    return value
}

x := identity(42)      // T — int deb avtomatik aniqlanadi
y := identity("hello") // T — string deb avtomatik aniqlanadi

any — eng keng (hech qanday cheklovsiz) constraint bo'lib, har qanday tipni qabul qiladi. Funksiya tanasida faqat barcha tiplar uchun umumiy bo'lgan amallarni (o'zlashtirish, nusxalash, reference tiplar uchun nil bilan solishtirish) bajarishga ruxsat beradi. any bilan cheklangan parametr ustida + yoki < kabi operatorlarni ishlatib bo'lmaydi, chunki ular barcha tiplarda (masalan, struct'larda) mavjud emas.

Funksiya bir nechta mustaqil type parameter'larni ham qabul qilishi mumkin:

example.go
func swap[T, U any](a T, b U) (U, T) {
    return b, a
}

a, b := swap(1, "hello") // a — string, b — int

Type argument'larni chaqiruvda aniq ko'rsatish ham mumkin:

example.go
x := identity[int](42) // aniq (explicit) ko'rsatish
x = identity(42)       // avtomatik aniqlash (type inference) — idiomatik usul

Constraint'lar: qaysi amallarga ruxsat berilgan?

Constraint shunchaki bezak emas. U kompilyatorga generic parametr bilan qanday amallarni bajarish mumkinligini qat'iy belgilab beradi.

comparable == va != ga ruxsat beradi:

example.go
func find[T comparable](slice []T, value T) int {
    for i, v := range slice {
        if v == value {
            return i
        }
    }
    return -1
}

find([]int{1, 2, 3}, 2)         // ok
find([]string{"a", "b"}, "a")   // ok
// find([][]int{{1}}, []int{1}) // kompilyatsiya xatosi: []int comparable emas

Goda slice'lar, map'lar va funksiyalar comparable emas — == ular uchun nil bilan taqqoslashdan tashqarida hatto aniqlanmagan — shuning uchun comparable ularni to'g'ri chiqarib tashlaydi. Bu amalda duch keladigan haqiqiy dizayn cheklovi: qo'shimcha qatlamsiz (masalan, slice'ni avval satr kalitiga serializatsiya qilish) slice qiymatlari ustidan generic to'plam yoki generic map-kalit tipi yoza olmaysiz.

Interface constraint'lar tipdan method set'ni qanoatlantirishni talab qiladi, aynan oddiy interface kabi, lekin bu yerda foyda shuki, metodning qaytariladigan tipi interface emas, aniq T bo'lib qolishi mumkin:

example.go
type Stringer interface {
    String() string
}

func describe[T Stringer](v T) string {
    return "value: " + v.String()
}

Union constraint'lar (texnik jihatdan, elementi tip atamalari to'plami bo'lgan interface) Tni qat'iy ro'yxatdagi tiplardan biri bilan cheklaydi:

example.go
type Number interface {
    int | int64 | float32 | float64
}

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

Deyarli har bir sonli-generic funksiya shu shaklni oladi, chunki + kabi arifmetik operatorlar birorta nomlangan interface'ning bir qismi emas — Goda intga o'rnatilgan interface{ Add(T) T } yo'q. Union constraint "qandaydir son tipi" bo'yicha generic funksiya yozishning va baribir +dan foydalanishga ruxsat olishning yagona yo'li.

Ordering alohida tilga olish foydali, chunki ekotizim uning ostida siljigan. Dastlabki generic kod (va internetda hamon ancha kod) constraints.Orderedni golang.org/x/exp/constraints tajribaviy modulidan import qiladi. Go 1.21 dan boshlab standart kutubxona ekvivalent constraint'ni tashqi bog'liqliksiz cmp paketida cmp.Ordered sifatida yetkazib beradi:

example.go
import "cmp"

func max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

Aslida, Go 1.21 dan boshlab bu funksiyani umuman yozishingiz kerak bo'lmasligi mumkin: min va max builtin'lari tilning o'ziga qo'shilgan va ko'z ko'rib turgan birorta type parameter'siz har qanday ordered tip bilan ishlaydi (max(a, b)). cmp.Orderedga min/max/clear qamramaydigan narsani yozayotganda murojaat qiling — generic binary search, generic sort kaliti, o'zingizning Clamp funksiyangiz — har bir taqqoslash uchun refleks sifatida emas.

Tip chiqarish va u qayerda to'xtaydi

Tipni avtomatik aniqlash (Type Inference) va uning chegaralari

Go kompilyatori funksiyaga uzatilgan argumentlarning tiplaridan T nima ekanini avtomatik aniqlaydi (type inference):

example.go
func max[T cmp.Ordered](a, b T) T { /* ... */ }

max(3, 5)       // T — int deb avtomatik aniqlanadi
max(3.14, 2.71) // T — float64 deb avtomatik aniqlanadi

Type inference har bir funksiya chaqiruviga nisbatan lokal ishlaydi: u siz uzatgan argumentlarning aniq tiplariga qaraydi. U natijadan qanday foydalanishingizni yoki boshqa satrlardagi kodni hisobga olmaydi. Bu esa ikkita holatda type argumentni qo'lda ko'rsatishni talab qiladi:

Birinchisi — bir-biriga mos kelmaydigan har xil tipli argumentlar uzatilganda:

example.go
max(3, 5.0) // Kompilyatsiya xatosi: tip mos kelmadi (biri int, ikkinchisi float64)

3 ham, 5.0 ham son bo'lsa-da, Go tiplarni avtomatik o'girmaydi (no implicit type conversion). Shuning uchun argumentlardan birini o'girish (max(float64(3), 5.0)) yoki tipni aniq ko'rsatish (max[float64](3, 5.0)) kerak.

Ikkinchi holat — type parameter faqatgina qaytariladigan qiymatda qatnashganda:

example.go
func zero[T any]() T {
    var z T
    return z
}

z := zero[int]() // T ni aniq ko'rsatish SHART — argument bo'lmagani uchun kompilyator uni aniqlay olmaydi

Bunday holatlarda tipni kvadrat qavslarda [int] deb ko'rsatish mutlaqo normal va zaruriy amaliyotdir.

Go 1.27 dan boshlab generic funksiyalarni darhol chaqirmasdan o'zgaruvchiga o'zlashtirish yoki boshqa funksiyalarga argument sifatida uzatishda ham tipni avtomatik aniqlash imkoniyatlari yanada kengaytirildi.

Alohida, type parameter'lar faqat funksiya darajasidagi tushuncha emasligini bilish foydali: Go 1.26 va 1.27 dan boshlab metodlar ham receiver'i allaqachon ega bo'lgan type parameter'lardan mustaqil ravishda o'zining type parameter'larini e'lon qila oladi. Bu ushbu bobdagi hamma narsadan alohida imkoniyat — u 21-bobda, Metodlar va receiver'lar bobida yoritilgan.

Funksiyalar type parameter sifatida, faqat ma'lumot emas

Type parameter'lar oddiy qiymatlarni ushlab turish bilan cheklanmaydi — any bilan cheklangan funksiya tipi boshqa funksiyani olib o'tishi mumkin, bu kichik taniqli higher-order yordamchilar oilasi uchun asos:

example.go
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, keep func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if keep(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](slice []T, initial U, combine func(U, T) U) U {
    acc := initial
    for _, v := range slice {
        acc = combine(acc, v)
    }
    return acc
}
example.go
names := Map(users, func(u User) string { return u.Name })
adults := Filter(users, func(u User) bool { return u.Age >= 18 })
total := Reduce(orders, 0.0, func(sum float64, o Order) float64 { return sum + o.Total })

Bu uchtasi odamlar qo'lda for sikllari bilan bajarishga murojaat qiladigan ishning katta qismini qoplaydi, va Go 1.21 dan beri standart kutubxona slice'ga xos holatlar uchun slices paketida ekvivalentlarni yetkazib beradi (slices.IndexFunc va boshqalar), ularni o'zingizniki yozishdan oldin tekshirish foydali — standart kutubxonadagi bitta yaxshi testlangan Map kod bazasi bo'ylab tarqalgan o'nlab biroz farq qiladiganlaridan qimmatroq.

Narxi haqida ham halol bo'lish kerak: Map, Filter va Reduce har chaqiruvda yangi slice (yoki closure holati) ajratadi, qo'lda yozilgan sikl esa har doim kamida shunday tez va bitta oddiy o'zgartirish uchun chaqiruv joyida ko'pincha aniqroq. Ularga kompozitsiyaning o'zi aniqlik qo'shganda — bir nechta o'zgartirishni zanjirlash yoki aks holda uch joyda takrorlanadigan besh qatorli siklni nomlash — murojaat qiling, for rangening standart o'rnini bosuvchi sifatida emas.

Keyingi bob funksiyalardan tiplarga o'tadi: generic struct'larni qanday qurish, comparable qachon majburiy va qachon shunchaki qulay va type parameter'lar ko'payganda generic tipning ochiq API'sini qanday o'qishli saqlash.