Generic үндэс
Та int тоонуудын хамгийн их утгыг олох функц бичлээ. Дараа нь float64-д мөн адил функц хэрэгтэй болов. Тэгээд string-д. Яг адилхан логик, зөвхөн төрөл л өөр — кодыг гурав давтах шаардлагатай болсон. Generic нь энэ асуудлыг шийддэг: нэг функц эсвэл бүтцийг олон төрлийн өгөгдөлд ашиглах боломж олгодог. Go 1.18-аас эхлэн generic дэмжигдсэн бөгөөд өнөөдөр Go-ийн стандарт library-д өргөн хэрэглэгддэг.
Type parameter — төрлийн параметр
Generic функцэд [T any] гэсэн type parameter нэмдэг. T нь төрлийн нэр (дурын үсэг байж болно), any нь хязгаарлалт (constraint):
package main
import "fmt"
// Generic функц: T дурын төрөл байж болно
func Хамгийн их[T int | float64 | string](а, б T) T {
if а > б {
return а
}
return б
}
func main() {
// int-тэй ажиллана
fmt.Println(Хамгийн их(10, 20)) // 20
// float64-тэй ажиллана
fmt.Println(Хамгийн их(3.14, 2.71)) // 3.14
// string-тэй ажиллана
fmt.Println(Хамгийн их("болд", "энхээ")) // энхээ
}
T int | float64 | string нь "T нь эдгээр гурван төрлийн аль нэг байж болно" гэсэн утгатай. Go compiler нь функцийг дуудах үед T-ийн утгыг автоматаар тодорхойлдог.
Constraint — хязгаарлалт
Constraint нь type parameter-т ямар төрлүүд зөвшөөрөгдөх вэ гэдгийг тодорхойлдог. golang.org/x/exp/constraints package болон cmp package-д бэлэн constraint-ууд байдаг. Өөрийн constraint интерфэйс болгон тодорхойлж болно:
package main
import "fmt"
// Тоон төрлүүдийн constraint
type Тоо interface {
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 |
float32 | float64
}
// Slice-ийн нийлбэр тооцоолох generic функц
func Нийлбэр[T Тоо](утгууд []T) T {
var нийт T
for _, v := range утгууд {
нийт += v
}
return нийт
}
// Дундаж утга олох
func Дундаж[T Тоо](утгууд []T) float64 {
if len(утгууд) == 0 {
return 0
}
нийт := Нийлбэр(утгууд)
return float64(нийт) / float64(len(утгууд))
}
func main() {
бүхэл := []int{10, 20, 30, 40, 50}
fmt.Println("Нийлбэр:", Нийлбэр(бүхэл)) // 150
fmt.Println("Дундаж:", Дундаж(бүхэл)) // 30
бутархай := []float64{1.5, 2.5, 3.0}
fmt.Println("Нийлбэр:", Нийлбэр(бутархай)) // 7
}
Тоо constraint нь бүх тоон төрлүүдийг нэгтгэсэн интерфэйс юм. Ингэснээр Нийлбэр функцийг нэг удаа бичиж бүх тоон төрлүүдэд ашиглах боломжтой болно.
Generic бүтэц — Generic struct
Функцээс гадна бүтэц (struct) болон slice-ийн бүтцийг generic болгож болно. Жишээ нь Stack өгөгдлийн бүтэц:
package main
import (
"fmt"
"errors"
)
// Generic Stack — дурын төрлийн өгөгдөл хадгалах
type Stack[T any] struct {
утгууд []T
}
func (s *Stack[T]) Push(утга T) {
s.утгууд = append(s.утгууд, утга)
}
func (s *Stack[T]) Pop() (T, error) {
var тэг T
if len(s.утгууд) == 0 {
return тэг, errors.New("stack хоосон байна")
}
сүүлийн := s.утгууд[len(s.утгууд)-1]
s.утгууд = s.утгууд[:len(s.утгууд)-1]
return сүүлийн, nil
}
func (s *Stack[T]) Хэмжээ() int {
return len(s.утгууд)
}
func main() {
// int Stack
бүхэлStack := &Stack[int]{}
бүхэлStack.Push(1)
бүхэлStack.Push(2)
бүхэлStack.Push(3)
утга, _ := бүхэлStack.Pop()
fmt.Println("Pop:", утга) // 3
fmt.Println("Хэмжээ:", бүхэлStack.Хэмжээ()) // 2
// string Stack — яг адилхан бүтэц, өөр төрөл
үгStack := &Stack[string]{}
үгStack.Push("сайн")
үгStack.Push("уу")
үг, _ := үгStack.Pop()
fmt.Println("Үг:", үг) // уу
}
Stack[int] болон Stack[string] нь яг адил кодоос, зөвхөн өгөгдлийн төрлөөр ялгардаг. Generic байхгүй байсан бол хоёр тусдаа бүтэц бичих байсан.
Map болон Filter — функциональ хэлбэр
Generic-ийн хамгийн өргөн хэрэглээний нэг бол slice дээр ажилладаг туслах функцүүд юм:
// Map: slice-ийн утга бүрийг хувиргана
func Map[T, U any](утгууд []T, fn func(T) U) []U {
үр_дүн := make([]U, len(утгууд))
for i, v := range утгууд {
үр_дүн[i] = fn(v)
}
return үр_дүн
}
// Filter: нөхцөл хангасан утгуудыг шүүнэ
func Filter[T any](утгууд []T, fn func(T) bool) []T {
var үр_дүн []T
for _, v := range утгууд {
if fn(v) {
үр_дүн = append(үр_дүн, v)
}
}
return үр_дүн
}
func main() {
тоонууд := []int{1, 2, 3, 4, 5, 6}
// Бүгдийг хоёр дахин нэмэгдүүлэх
хоёр дахин := Map(тоонууд, func(n int) int { return n * 2 })
fmt.Println(хоёр дахин) // [2 4 6 8 10 12]
// Зөвхөн тэгш тоонуудыг авах
тэгш := Filter(тоонууд, func(n int) bool { return n%2 == 0 })
fmt.Println(тэгш) // [2 4 6]
// string slice-д map хэрэглэх
нэрнүүд := []string{"болд", "энхээ", "дорж"}
урттай := Map(нэрнүүд, func(нэр string) int { return len(нэр) })
fmt.Println(урттай) // [4 5 4]
}
Энэ хэв маяг нь Go 1.21-ийн slices package-д бэлнээр орсон — slices.Map, slices.Contains, slices.Sort зэрэг generic функцүүд ашиглахад бэлэн байна.
Дараагийн хичээлд:
Generic-ийн дараа Go-ийн нэг чухал хэрэгсэл болох context package-ийг судална. Context нь хүсэлтийн хугацааг хязгаарлах, цуцлах дохио дамжуулах, request-ийн туршид өгөгдөл хуваалцах боломж олгодог — production програмд зайлшгүй шаардлагатай.