Shanraq.org Shanraq.org
Әрі қарай қайда: дженериктер және курстан кейінгі жол
IT

Go тілі: нөлден өз блогыңызға дейін 50-сабақ (барлығы 50)

Әрі қарай қайда: дженериктер және курстан кейінгі жол

Go курсының елуінші, соңғы сабағы. Дженериктер — үшеудің орнына бір функция, ал типке қойылған шартты компилятор тексереді: `string` іске қосылғанға дейін өтпейтіні өлшенді. Олардың `any`-ден несімен жақсы екені де өлшенді: 534 наносекунд пен 1801.

Не үшін керек

Курс бітіп келеді. Блог жұмыс істейді, өз доменінде HTTPS астында тұр, ұқыпты тоқтайды әрі қайта жүктеуден аман өтеді. Екі әңгіме қалды: өтпеген нәрсеміз туралы әрі әрі қарай қайда бару туралы.

Өтпегеніміз — дженериктер, көп типке бір код жазу тәсілі. Бұл курстың соңғы тақырыбы болуы кездейсоқ емес: онсыз ұзақ өмір сүруге болады, ал түсіну үш дерлік бірдей функцияны қолмен жазып шыққанда оңайырақ.

Бірден тұтас

Жаңа қалта, go mod init sabaq47:

package main

import (
	"fmt"
	"sort"
)

// Number — тип емес, шарт: мұнда плюспен қосылатын әрі сан сияқты ұстанатын
// нәрсенің бәрі жарайды. Тильда «іші осындай өз типтерің де» дегенді білдіреді.
type Number interface {
	~int | ~int64 | ~float64
}

// Sum кез келген сандар тілімін қосады. Дженериктерге дейін мұндай функцияны
// үшеу жазатын: int-ке, int64-ке әрі float64-ке — денесі бірдей.
func Sum[T Number](xs []T) T {
	var total T
	for _, x := range xs {
		total += x
	}
	return total
}

// Keys сөздіктің кілттерін қайтарады. K comparable — өйткені сөздіктің кілті
// тек салыстырылатын нәрсе бола алады; V any — мән бізге бәрібір.
func Keys[K comparable, V any](m map[K]V) []K {
	out := make([]K, 0, len(m))
	for k := range m {
		out = append(out, k)
	}
	return out
}

// Views — горутиналар сабағындағы қарау санағышымыз, тек енді ол немен
// санайтынын білмейді: мақаламен, тегпен әлде оқырманмен.
type Views[K comparable] struct {
	counts map[K]int
}

func NewViews[K comparable]() *Views[K] {
	return &Views[K]{counts: make(map[K]int)}
}

func (v *Views[K]) Add(key K)       { v.counts[key]++ }
func (v *Views[K]) Count(key K) int { return v.counts[key] }

// Балмен қойылған баға — өз типі, бірақ іші int. Number-дегі тильда оған да рұқсат етеді.
type Score int

func main() {
	fmt.Println("== үшеудің орнына бір функция")
	fmt.Println("бүтін:      ", Sum([]int{2, 3, 4}))
	fmt.Println("бөлшек:     ", Sum([]float64{0.5, 0.25}))
	fmt.Println("өз типі:    ", Sum([]Score{10, 20}))

	fmt.Println()
	fmt.Println("== кез келген сөздіктің кілттері")
	ages := map[string]int{"Абай": 41, "Мұқағали": 45}
	names := Keys(ages)
	sort.Strings(names)
	fmt.Println("жолдар:     ", names)
	years := Keys(map[int]string{1845: "Абай", 1931: "Мұқағали"})
	sort.Ints(years)
	fmt.Println("сандар:     ", years)

	fmt.Println()
	fmt.Println("== немен санайтыны бәрібір санағыш")
	byArticle := NewViews[string]()
	byArticle.Add("bir-jazba")
	byArticle.Add("bir-jazba")
	fmt.Println("мақала бойынша:", byArticle.Count("bir-jazba"))

	byYear := NewViews[int]()
	byYear.Add(2026)
	fmt.Println("жыл бойынша:   ", byYear.Count(2026))
}

Шығатыны:

== үшеудің орнына бір функция
бүтін:       9
бөлшек:      0.75
өз типі:     30

== кез келген сөздіктің кілттері
жолдар:      [Абай Мұқағали]
сандар:      [1845 1931]

== немен санайтыны бәрібір санағыш
мақала бойынша: 2
жыл бойынша:    1

Талдау

Тип параметрі — тағы бір аргумент, тек ол тип

Кәдімгі функция мәндерді қабылдайды. Дженерик оған қоса типті қабылдайды — кәдімгі жақшалардың алдындағы тік жақшада:

func Sum[T Number](xs []T) T

T — шақыру орнында белгілі болатын типтің аты. []int-пен шақырдыңыз — T int болды; []float64-пен шақырдыңыз — float64 болды. Компилятор типті өзі қояды, сондықтан мысалда еш жерде Sum[int](...) деп жазылмаған: ол аргументтен көрініп тұр.

Дженериктерсіз бұл — денесі бірдей үш функция: SumInt, SumInt64, SumFloat. Денедегі бірінші қате — үш түзету, әрі біреуін бір күні ұмытады.

Шарт — дженериктегі ең бастысы

Number — тип те емес, ескі мағынадағы интерфейс те емес. Бұл — шарт: T-ға не болуға рұқсат етілгенінің тізімі.

type Number interface {
	~int | ~int64 | ~float64
}

Тильда «іші осындай кез келген өз типің де» дегенді білдіреді. Сондықтан мысалда type Score int деп жарияланған Score те қосылады.

Шарт — әшекей емес. Онсыз функцияның денесі жиналмас еді: компилятор T-ға + қолдануға болатынын білмейді. Әрі ол шақыруды да тексереді:

$ go build ./...
./main.go:20:17: string does not satisfy Number (string missing in ~int | ~int64 | ~float64)

Жолдар плюспен қосылады, бірақ сан емес. Қате іске қосылғанға дейін келеді, бірінші сәтсіз кеште емес, — бұл істің бүкіл мәні.

Бұл any-ден несімен жақсы

Дженериктерге дейін әртүрлі нәрсені any арқылы әрі орнында типті талдау арқылы қосатын. Жұмыс істейді, бірақ оның ақысы бар. Мың саннан тұратын тілімде өлшенді:

BenchmarkSumGeneric-8    2238902     533.7 ns/op     0 B/op   0 allocs/op
BenchmarkSumAny-8         663471    1801   ns/op     0 B/op   0 allocs/op

534 наносекунд пен 1801 — үш жарым есе. Айырмашылық жадта емес (екі жағдайда да бөлу жоқ), жұмыста: any нұсқасында әр элементте тип талданады, ал дженерик типті алдын ала біледі әрі сандарды тікелей қосады.

Мұны өзіңіз өлшеген жөн: go test -bench . -benchmem — тесттер сабағындағы сол команда, тек жылдамдық туралы.

Дженериктер қашан керек емес

Жаңадан бастаушының азғыруы — бәрін дженерик қылу. Одан ұстап қалатын үш ереже:

  • Бір типке арналған бір функция — себеп емес. func words(s string) int өзі болып қала береді; мұнда тип параметрі жақша қосады да, басқа ештеңе бермейді.
  • Интерфейс жиі жақсырақ. Әртүрлі типке әртүрлі мінез керек болса — бұл интерфейс, интерфейстер сабағындағы blog.Store сияқты. Дженерик — әртүрлі дерек типтерінің үстіндегі бірдей мінез туралы.
  • Оқылымдылық қымбатырақ. func Map[T, U any](xs []T, f func(T) U) []U — әдемі, бірақ мұндайдың үш ішінде тұрған шақыруы кәдімгі циклден нашар оқылады. Go-да цикл — ұят емес.

Жақсы белгі: сіз екінші дерлік бірдей функцияны жазып та қойдыңыз әрі үшіншісін көшіріп жатырсыз. Міне енді.

Стандартты кітапханада олар бар

Жазғың келетіннің жартысын жазудың қажеті жоқ:

  • slicesslices.Sort, slices.Contains, slices.Index, slices.Max;
  • mapsmaps.Keys, maps.Values. Бір нәзіктік: олар тілім емес, тізбек қайтарады, сондықтан тілімді былай алады: keys := slices.Collect(maps.Keys(m)). Мысалдағы Keys-іміз — бірден тілім қайтаратын оқу көшірмесі;
  • cmp — салыстыру әрі cmp.Or;
  • sync.OnceValue — бір рет санап, дайынын қайтару.

Әрі қарай қайда бару керек

Курс бітті, тіл — жоқ. Жеті бағыт, әрқайсысында ол не үшін керегі туралы бір жол:

  1. slices, maps, cmp — енді оқи алатыныңыз: олар тұтасымен дженериктерде.
  2. SQLite орнына Postgres — сол database/sql, басқа драйвер әрі нағыз қосылымдар пулы. Блогтағы SQLite таңдау болатын, заң емес.
  3. errgroup мен sync.WaitGroup — горутина екеу емес, қырық болғанда әрі бәрі бітуге тиіс болғанда.
  4. net/http/pprof — бағдарлама уақытты қайда өткізетінін болжамай білу.
  5. go test -fuzz — кірісті өзі ойлап табатын әрі сіз ойламағанды табатын тесттер.
  6. Метрикалар мен трассировкаexpvar, Prometheus, OpenTelemetry: қанша сұраныс, қайсысы баяу, қай жерде күтті.
  7. Түймемен жинау — GitHub Actions: әр коммитке тесттер әрі бір командамен деплой.

Оқу реті мынадай: go.dev/doc/effective_go, содан кейін Go by Example, одан соң бөтен код — стандартты кітапханадан бастап, ол оқылымды.

Блогта не салуға болады

Курстағы блог — аяқталған нәрсе емес, іргетас. Әрі қарай не сұранып тұр, күрделілігі өсу ретімен:

  • әлеуметтік желіге арналған суретog:image, сілтеме жол емес, сілтеме болып көрінуі үшін;
  • жобалар — мақала бірден бәріне көрінбейді; бір өріс әрі сұраныстағы бір тексеру;
  • пікірлер — форма, кесте, модерация әрі тағы да CSRF;
  • бірнеше автор — құқықтар бізде бар, автор беті жетіспейді;
  • хаттар — поштаны растау әрі құпиясөзді қалпына келтіру.

Сабақ картасы

Сабақ картасы: тип параметрі, шарт және әрі қарайғы жол

Сіз енді не істей аласыз

Елу сабақ бұрын біз төрт жолдан тұратын бағдарлама жаздық. Қазір сіздің артыңызда блог тұр: ол мақалаларды көшірулері бар дерекқорда сақтайды, олардан іздейді, адамдарды құпиясөзбен кіргізеді әрі сессияларды ұстайды, құқықтарды ажыратады, суреттерді қабылдайды, JSON береді, жарыстарға тексерілген тесттермен қапталған, өз доменінде HTTPS-пен жұмыс істейді, ұқыпты тоқтайды әрі сақтық көшірме жасай алады.

Репозиторийдегі отыз төрт қадам — әр сабақтағы сол блог, әрі кез келгенін бір командамен іске қосуға болады. Бірдеңе ұмытылса, оларға оралыңыз: ондағы код мәтіндегімен бірдей.

Өз сөзіңізбен айтыңыз

Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.

  1. Тип шақыруда бәрібір қойылатын болса, дженерикке типке қойылған шарт не үшін керек?
  2. Типті талдайтын any неге тип параметрінен баяу?
  3. Дженерик мүмкін болса да, қашан керек емес?

Тапсырма

Міндетті. Блогыңыздан денесі дерлік бірдей екі функцияны тауып, оларды тип параметрі бар біреуге біріктіріңіз. Ондай болмаса — Filter[T any](xs []T, keep func(T) bool) []T жазып, оны мақалалар тізіміне қолданыңыз.

Қалауыңызша.

  • Өз нұсқаңызды any нұсқасына қарсы өлшеңіз: go test -bench . -benchmem.
  • Горутиналар сабағындағы қарау санағышын кез келген нәрсені санайтындай етіп қайта жазыңыз — ішінде мьютексі бар.
  • Өз шағын көмекшілеріңізді slices пен maps-қа ауыстырып, қанша жол кеткенін қараңыз.

Блогта бұл қайда тұрады

Еш жерде — әрі бұл дұрыс аяқталу. Уәде етілген блогтың өзегі дайын: ол жұмыс істейді, шығарылған әрі оны көрсетуге болады. Дженериктер оған керек емес, әрі сабақ үшін оларды қосу шыншыл болмас еді.

Жақсарту тізімі бар — ол кез келген жұмыс істеп тұрған жүйеде бар. Сабақтарда аталғаннан: кіруді теруден қорғау, мерзімі өткен сессияларды тазалау әрі «барлық жерден шығу» түймесі, мақалалар тізімін беттерге бөлу, мақаламен бірге суреттерді өшіру, іздеудегі қазақ морфологиясы, тексерудегі дерекқордың дайындығы. Бұл — курстың қарызы емес, кәдімгі пайдалану кезегі.

Әрі қарай блогты курс емес, сіз өзгертесіз.

Жауаптар

Жауаптарды көрсету
  1. Өйткені шартсыз компилятор T-мен не істеуге болатынын білмейді: қосу, салыстыру, әдіс шақыру. Шарт — функцияның денесіне берілген рұқсат әрі оны шақырушыға қойылған тексеру.
  2. Өйткені any нұсқасында тип әр элементте, жұмыс кезінде талданады. Дженерик типті жинау кезінде біледі — өлшенді: 534 наносекунд пен 1801.
  3. Функция бір типке керек болғанда; типтерге әртүрлі мінез керек болғанда — онда интерфейс; әрі тип параметрінен оқылымдылық ұтқанынан көбірек ұтылғанда.

Дереккөздер

Мәтінде қате не теру қатесі кездессе, бізге айтыңыз

Тапсырманы тексеру

Алдымен VS Code-та шешіп, іске қосыңыз — редактор қатені сол жерде көрсетеді. Дайын шешімді осында қойыңыз. Тексеретін — модель: ол қатені атап көрсетеді, бірақ дайын жауапты бермейді.

Тексеру үшін кіру керек. Кіру

Пікірлер (0)

Әзірге пікір жоқ. Бірінші болыңыз.