- наш обучающий телеграм канал для Golang разработчиков
- папка с самыми полезными ресурсами для Golang разработчиков
Go статически типизированный язык. Это значит, что типы переменных проверяются на этапе компиляции. Встроенные типы в Go, такие как мапы, слайсы каналы, а также встроенные функции, например len и make умеют работать с разными типами. Но до версии пользовательские типы так не умели.
Это значит, что в Go, если я реализую бинарное дерево для, например, типа int:
и потом понадобится реализовать дерево для strings или float, и мне будет нудна типобезопасность, то придется написать кастомный код для каждого типа отдельно. Такой подход подвержен ошибкам, очень многословен и нарушает принцип DRY(не повторять себя).
С другой стороны, я могу изменить свое бинарное дерево так, чтобы оно работало с типом interface{}. Это равнозначно разрешению на работу с любым типом. Но так я потеряю возможность проверки типов во время компиляции, а проверка типов при компиляции - одно из главных преимуществ Go. Кроме того, в Go нет возможности обработки слайса любого типа - слайс []string или []int нельзя назначить переменной типа interface{}.
В результате, до Go и дженериков, общие алгоритмы для слайсов, такие как map, reduce, filter, реализовывались в ручную для каждого типа отдельно. Это расстраивало разработчиков и было причиной критики языка Go.
Разработчики иметь возможность писать функции и структуры, в которых конкретный тип параметра или переменной можно было бы указать при использовании, при этом сохранить типобезопасность на этапе компиляции.
Для примера, возьмем две функции которые суммируют слайсы map[string]int64 и map[string]float64 соответственно:
copy
1// SumInts складывает вместе значения m.
2func SumInts(m map[string]int64) int64 {
3 var s int64
4 for _, v := range m {
5 s = v
6 }
7 return s
8}
9
10// SumFloats складывает вместе значения m.
11func SumFloats(m map[string]float64) float64 {
12 var s float64
13 for _, v := range m {
14 s = v
15 }
16 return s
17}
Было бы круто один раз написать функцию, которая умеет суммировать все значения любой числовой мапы, вмето написания функции под каждый тип.
Или еще один пример - две функции, которые умножают значения типа int и int16:
copy
1func DoubleInt(value int) int {
2 return value * 2
3}
4
5func DoubleInt16(value int16) int16 {
6 return value * 2
7}
Возможность написать одну, которая перемножают любые типы, это очень удобно. Неправда ли?
Почему так долго ждали?
Если аргументы в пользу дженериков такая так убедительны, то почему команде разработки Go понадобилось больше 10 лет для их реализации? На самом деле, эту проблему не так просто решить. Go делает упор на быстроте компиляции, понятном и читабельном коде, быстроте исполнения. Различные предполагаемы реализации ставили под угрозу одно ил несколько из этих преимуществ.
Но, как это часто бывает с открытым ПО, коллективное решение проблемы в конечном итоге привело к приемлемой реализации. Решение получилось универсальным, быстрым и эффективным. Но при этом достаточно гибким, чтобы соответствовать требованиям.
Дженерики а Go - резюме
Начиная с версии язык Go был расширен, чтобы дать возможность добавлять явно определенные структурные ограничения, называемые параметрами типа(type parameters), к объявлениям функций и объявлениям типов.
copy
1func F[T any](p T) { … }
2
3type M[T any] []
Список параметров типа [T any] использует квадратные скобки, но в остальном выглядит как обычный список параметров. Эти параметры типа затем могут использоваться обычными параметрами функции или в теле функции.
Общий(дженерик) код ожидает, что аргументы типа будут соответствовать определенным требованиям, которые называются ограничениями(constraints). Для каждого параметра типа должно быть определено ограничение:
copy
1func F[T Constraint](p T) {
2 // ...
3 }
Эти ограничение не более чем интерфейсные типы.
Ограничение параметра типа может могут ограничивать набор аргументов типа одним из трех способов:
произвольный(arbitrary) тип T ограничивается этим типом:
copy
1func F[T MyConstraint](p T) {
2 // ...
3 }
элемент приближения(approximation element) ~T ограничивается всеми типами, базовым типом которых является T
copy
1func F[T ~int] (p T) {
2 // ...
3 }
объединенный элемент T1 | T2 | … ограничивается любым из перечисленных элементов
copy
1func F[T ~int | ~string | ~float64] (p T) {
2 // ...
3 }
Когда универсальные функции и типы используют операторы для определенных параметров типа, эти параметры должны удовлетворять интерфейсу, определяемому ограничением параметра.
И самое главное, дизайн дженериков полностью обратно совмести с Go 1.
5,349 views
229
49
2 months ago 00:03:46 1
Лечение гепатита C при ВИЧ-инфекции: личный опыт
2 months ago 00:12:12 4K
Generics в Java. Что это и как работает?
2 months ago 00:20:55 16
Разоблачение Михаила Русакова. Он вас не обучит до уровня Junoir на C#!
2 months ago 00:40:17 9
[Доктор Утин] Лекарства в России после санкций. Фармаколог Юрий Киселев. @DoctorUtin
2 months ago 00:21:54 12
[Unreal Engine] Using Simple Generic Materials to Improve UI Performance | Unreal Fest 2024
2 months ago 01:42:03 4
Typescript - декораторы и дженерики с примером их использования // курс «Разработчик »