Go’da generics bilan ishlash
Generics tur parametrlari (type parameters) orqali bir xil kodni bir nechta tur bilan xavfsiz ishlatish imkonini beradi. Masalan, int va float64 qiymatlarini jamlaydigan funksiyalar bir xil mantiqqa ega bo‘lsa, ularni bitta generic funksiya bilan ifodalash mumkin.
Go generics’ni 1.18 versiyasidan boshlab qo‘llab-quvvatlaydi. Har bir funksiyani generic qilish shart emas. Generics kod takrorini kamaytirsa va turlar orasidagi bog‘lanishni aniq saqlasa foydali bo‘ladi.
Generics nima uchun kerak?
Tasavvur qiling, buyurtmadagi miqdorlar int, narxlar esa float64 ko‘rinishida saqlanadi. Ikkala slice elementlarini jamlash uchun alohida funksiyalar yozish mumkin:
func intlarniJamlash(values []int) int {
var total int
for _, value := range values {
total += value
}
return total
}
func floatlarniJamlash(values []float64) float64 {
var total float64
for _, value := range values {
total += value
}
return total
}
Bu funksiyalarda faqat turlar farq qiladi. Algoritm esa bir xil. Yana bir son turi kerak bo‘lsa, shu kodning navbatdagi nusxasi paydo bo‘ladi.
Generic funksiya tur farqini parametrga aylantiradi:
package main
import "fmt"
type Number interface {
~int | ~int64 | ~float64
}
func jamlash[T Number](values []T) T {
var total T
for _, value := range values {
total += value
}
return total
}
func main() {
miqdorlar := []int{2, 3, 5}
narxlar := []float64{12.5, 7.25, 10}
fmt.Println(jamlash(miqdorlar))
fmt.Println(jamlash(narxlar))
}
Natija:
jamlash() bitta algoritmni tur xavfsizligini yo‘qotmasdan turli sonlar uchun ishlatdi. Funksiya []int qabul qilsa int, []float64 qabul qilsa float64 qaytaradi.
Tur parametri va constraint
Generic funksiya nomidan keyingi [] ichida tur parametri yoziladi:
Bu yozuvda:
T— tur parametri;Number—Tqabul qilishi mumkin bo‘lgan turlarni belgilovchi constraint;[]T— elementlariTturida bo‘lgan slice;- oxirgi
T— funksiya qaytaradigan tur.
Constraint tur parametrida qaysi amallarni bajarish mumkinligini ham belgilaydi. Number ichidagi barcha turlar + operatorini qo‘llab-quvvatlagani uchun total += value yozuvi to‘g‘ri.
| belgisi ruxsat berilgan turlarni birlashtiradi. ~int yozuvi faqat intni emas, asosiy turi int bo‘lgan nomlangan turlarni ham qabul qiladi:
Quantity turi ~int talabiga mos keladi. Agar constraintda faqat int yozilsa, bunday nomlangan tur unga mos kelmaydi.
Tur inferensiyasi
Generic funksiya chaqirilganda tur argumentini aniq yozish mumkin:
Ko‘p holatda Go argumentlardan tur argumentini o‘zi aniqlaydi:
Har ikkala chaqiruvda T turi int. Ikkinchi ko‘rinish tur inferensiyasi (type inference) deyiladi.
Ba’zan kompilyator turni aniqlay olmaydi. Masalan, tur parametriga bog‘liq argument bo‘lmasa, tur argumenti aniq beriladi:
zero() argument qabul qilmagani uchun Tni qiymatdan aniqlab bo‘lmaydi. Shu sababli [int] yoki [string] yozilishi kerak.
any constrainti
any har qanday turga ruxsat beradigan constraint. U interface{} uchun alias hisoblanadi.
Quyidagi funksiya slice nusxasini yaratadi. Algoritm element ustida arifmetik yoki taqqoslash amali bajarmagani uchun turga qo‘shimcha talab kerak emas:
package main
import "fmt"
func nusxalash[T any](values []T) []T {
result := make([]T, len(values))
copy(result, values)
return result
}
func main() {
sonlar := nusxalash([]int{10, 20, 30})
sozlar := nusxalash([]string{"Go", "Generics"})
fmt.Println(sonlar)
fmt.Println(sozlar)
}
Natija:
any barcha amallarga ruxsat beradi degani emas. T any bo‘lsa, a + b, a > b yoki a == b yozib bo‘lmaydi. Chunki barcha Go turlari bu operatorlarni qo‘llab-quvvatlamaydi.
Oddiy any qiymati va T any tur parametri ham bir xil vazifani bajarmaydi:
Birinchi funksiya aniq tur haqidagi ma’lumotni yo‘qotadi va any qaytaradi. Ikkinchi funksiya kiruvchi slice element turini saqlab, aynan shu turni qaytaradi.
comparable constrainti
comparable == va != bilan solishtirish mumkin bo‘lgan turlar uchun tayyor constraint:
package main
import "fmt"
func mavjud[T comparable](values []T, target T) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}
func main() {
fmt.Println(mavjud([]string{"go", "rust"}, "go"))
fmt.Println(mavjud([]int{10, 20}, 30))
}
Natija:
comparable tartib bo‘yicha solishtirishni anglatmaydi. U < yoki > operatorlariga ruxsat bermaydi. Slice, map va funksiyalarni == bilan solishtirib bo‘lmagani uchun ular comparable constraintiga mos kelmaydi.
Bir nechta tur parametri
Funksiya bir nechta tur parametri qabul qilishi mumkin. Masalan, map kalitlarini slice ko‘rinishida olishda kalit va qiymat turlari boshqa-boshqa bo‘ladi:
package main
import "fmt"
func kalitlar[K comparable, V any](items map[K]V) []K {
result := make([]K, 0, len(items))
for key := range items {
result = append(result, key)
}
return result
}
func main() {
ages := map[string]int{
"Ali": 24,
"Vali": 31,
}
fmt.Println(kalitlar(ages))
}
K comparable bo‘lishi shart, chunki Go map kaliti faqat solishtiriladigan turda bo‘ladi. V ustida hech qanday amal bajarilmagani uchun unga any yetarli.
Map elementlari tartiblanmagan. Shu sababli natijadagi kalitlar tartibi har ishga tushirishda farq qilishi mumkin.
Generic type
Tur parametrini faqat funksiyada emas, type e’lonida ham ishlatish mumkin. Generic type bir xil ma’lumot tuzilmasini turli element turlari bilan yaratishga yordam beradi.
Quyidagi Stack qiymatlarni oxirgi qo‘shilganidan boshlab qaytaradi:
package main
import "fmt"
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(value T) {
s.items = append(s.items, value)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
last := len(s.items) - 1
value := s.items[last]
s.items = s.items[:last]
return value, true
}
func main() {
var stack Stack[string]
stack.Push("birinchi")
stack.Push("ikkinchi")
value, ok := stack.Pop()
if ok {
fmt.Println(value)
}
}
Natija:
Stack[T] ichidagi items faqat T qiymatlarini saqlaydi. Stack[string] yaratilganda Push() faqat string qabul qiladi va Pop() string qaytaradi. Stack[int] esa shu tuzilma va metodlarni int bilan ishlatadi.
Pop() bo‘sh stack uchun Tning nol qiymati va false qaytaradi. Generic kodda Tning nol qiymatini olish uchun var zero T yozuvi ishlatiladi.
Generic type metodining receiverida ham tur parametri ko‘rsatiladi:
Bu yerdagi T Stack e’lonidagi constraintdan foydalanadi. Receiverda constraintni qayta yozish shart emas.
Generics qachon kerak emas?
Generics quyidagi holatlarda ortiqcha bo‘lishi mumkin:
- kod faqat bitta aniq tur bilan ishlasa;
- turlar bir xil algoritm emas, turli xatti-harakat talab qilsa;
- oddiy interface kerakli xatti-harakatni aniqroq ifodalasa;
- tur parametri kodni kamaytirmay, aksincha o‘qishni qiyinlashtirsa.
Constraintni imkon qadar tor tanlang. Funksiyaga faqat tenglik tekshiruvi kerak bo‘lsa comparable, hech qanday turga xos amal kerak bo‘lmasa any yetarli. Keraksiz turlarni katta constraintga qo‘shish funksiyaning vazifasini noaniq qiladi.
Xulosa
- Generics bir xil algoritmni bir nechta tur bilan xavfsiz qayta ishlatadi.
- Tur parametri
[]ichida, uning constrainti esa parametr nomidan keyin yoziladi. - Go ko‘p hollarda tur argumentini funksiya argumentlaridan aniqlaydi.
anybarcha turlarni,comparableesa==va!=bilan solishtiriladigan turlarni qabul qiladi.- Generic type turli elementlar uchun qayta ishlatiladigan ma’lumot tuzilmasini yaratadi.
- Generics kodni aniq soddalashtirgandagina ishlatilishi kerak.