Перейти до основного вмісту
Модуль 10 · Урок 3 із 548/50 у курсі~15 min
Модульні уроки (3/5)

Generics (Go 1.18+)

З Go 1.18 (березень 2022) доступні параметри типу: функції та типи можна параметризувати над типами. Вони дозволяють писати Map, Filter, Set[T], LinkedList[T] без дублювання коду та без interface{} + підтвердження типу виконання.

Родова функція: синтаксис

Go
func Map[T, U any](s []T, f func(T) U) []U {
    out := make([]U, len(s))
    for i, v := range s {
        out[i] = f(v)
    }
    return out
}

doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })
// doubled is []int{2, 4, 6}

Три нові елементи:

  • [T, U any] після імені: оголошує параметри типу з їхніми обмеженнями.
  • any = обмеження, яке приймає будь-який тип (псевдонім interface{} з Go 1.18).
  • Висновок: виклик Map([]int{...}, ...) вам не потрібно писати Map[int, int](...): компілятор робить висновок T=int, U=int.

Іноді логічного висновку недостатньо (наприклад, конструктори без аргументів T), і потрібна явна інстанція: New[*Server]().

Вбудовані обмеження

ОбмеженняЗначення
anyБудь-який тип (== interface{}).
comparableТипи з == та !=: числові, рядкові, логічні, покажчики, канали, порівняльні масиви/структури.

comparable дозволяє рівність, але не < або >.

Go
func Index[T comparable](s []T, x T) int {
    for i, v := range s {
        if v == x { return i }
    }
    return -1
}

Для </> вам потрібне спеціальне обмеження.

Спеціальне обмеження: об’єднання типів

Обмеження — це інтерфейс із пунктами «елемент типу»:

Go
type Ordered interface {
    ~int | ~int64 | ~float64 | ~string
}

func Min[T Ordered](a, b T) T {
    if a < b { return a }
    return b
}
  • | = об'єднання: приймає будь-який із перелічених типів.
  • ~int = "int або будь-який тип, визначений як type X int" (з тим самим базовим типом). Без ~, лише точне int.

Обмеження також може змішувати методи та елементи типу:

Go
type Stringable interface {
    ~string
    Len() int
}

Родові типи

Не тільки функції: структури та інтерфейси також можуть мати параметри типу.

Go
type Set[T comparable] struct {
    m map[T]struct{}
}

func NewSet[T comparable]() *Set[T] {
    return &Set[T]{m: make(map[T]struct{})}
}

func (s *Set[T]) Add(v T) { s.m[v] = struct{}{} }
func (s *Set[T]) Has(v T) bool { _, ok := s.m[v]; return ok }

s := NewSet[string]()
s.Add("ada")

Методи НЕ МОЖУТЬ додавати параметри типу, крім параметрів типу: func (s *Set[T]) MapTo[U any](...) не компілюється (навмисне обмеження дизайну).

Коли використовувати дженерики

Так, коли:

  • Логіка справді ідентична для кількох типів (колекції, алгоритми, помічники, такі як Map/Filter/Reduce).
  • Альтернативою може бути дублювання коду або використання interface{} + твердження типу (втрата безпеки типу).

Ні, коли:

  • Інтерфейс з одним або двома методами є достатнім і більш читабельним (io.Reader, fmt.Stringer).
  • Ви параметризуєте «тому що можете»: єдиний варіант використання → створіть його конкретну версію.

вправи

вправи#go.m10.l3.e1
Спроби: 0Завантаження…

Definisci la funzione generica Min[T Ordered](a, b T) T che ritorna il minore usando l'operatore <. Il constraint Ordered è già fornito.

Завантаження редактора…

Рішення доступне після 3 спроб

вправи#go.m10.l3.e2
Спроби: 0Завантаження…

Implementa Map[T, U any](s []T, f func(T) U) []U: applica f a ogni elemento e restituisce il nuovo slice.

Завантаження редактора…

Рішення доступне після 3 спроб

Quiz#go.m10.l3.e3
Готовий

Яке вбудоване обмеження погоджується з оператором == ma NON l'operatore <?

Go
func Index[T ???](s []T, x T) int { ... }
Варіанти відповідей