Скоро выйдет релиз 1.18 в котором появятся долгожданные дженерики. Они позволят сделать универсальные методы. Я написал пару примеров для себя. Может быть они будут интересны кому-нибудь ещё.
Интерфейсы any, comparable, constraints. и ~
Появились новые ключевые слова
any - аналог interface{}
. Это ключевое слово можно использовать в любом месте. Например при определении типа переменной или при опрредении типа поля в структуре. Вот такой код ошибок не вызовет:
func TestDo(v any) any {
var k any
k = 10
return k
}
type Test interface {
Some() any
}
comparable - это интерфейс который определяет типы которые могут быть сравнены с помощью == и !=. Переменные такого типа создать нельзя. (var j comparable
будет вызывать ошибку.)
Появилась возможность определять интерфейсы, которые можно будет использовать в параметризованных функциях и типах. Переменные такого типа создать нельзя. (var j Int
будет вызывать ошибку.)
type Int interface {
int | int32 | int64
}
Под данный интерфейс подходят только описанные в нём типы.
Если добавить знак ~ перед типами то интерфейсу будут соотвествовать и производные типы, например myInt из примера ниже:
type Int interface {
~int | ~int32 | ~int64
}
type myInt int
Разработчики golang создали для нас уже готовый набор интерфейсов, который очень удобно использовать: https://pkg.go.dev/golang.org/x/exp/constraints
Параметризованные функции
Давайте рассмотрим пример функции Max. Эта функция возвращает максимум из двух переданных значений. Причём тип может быть любым.
import "constraints"
func Max[T constraints.Ordered](a T, b T) T {
if a > b {
return a
}
return b
}
Ограничения на используемые типы описываются в квадратных скобочках. В качестве ограничения для типов можно использовать любой интерфейс и особые интерфейсы описанные выше.
Давайте проведём эксперимент и посмотрим какой тип будет у параметра параметризованной функции:
package main
import (
"fmt"
"reflect"
)
func TypeTest[T any](a T) {
fmt.Println(reflect.TypeOf(a))
}
func main() {
TypeTest("abc")
TypeTest(1.0)
TypeTest(1)
}
Результат соответствует ожиданиям:
string
float64
int
Для слайсов и мапов был создан набор готовых полезных функций:
https://pkg.go.dev/golang.org/x/exp/slices
https://pkg.go.dev/golang.org/x/exp/maps
Параметризованные типы
Давайте сделаем пример мапы и добавим пару методов. Один из которых вытащит все ключи, а второй напишет в консоль тип.
package main
import (
"fmt"
"reflect"
)
type myMap[K comparable, V any] map[K]V
func (m myMap[K, V]) Keys() []K {
res := make([]K, 0, len(m))
for k := range m {
res = append(res, k)
}
return res
}
func (m myMap[K, V]) Type() {
fmt.Println(reflect.TypeOf(m))
}
func main() {
mp := myMap[int, string]{5: "sd"}
fmt.Println(mp.Keys())
mp.Type()
}
Вывод:
[5]
main.myMap[int,string]
Немного моих мыслей
Сейчас появятся библиотеки которые будут реализовывать функции Max, Min, IIF и подобные. Вот мой пример
А так же появятся библиотеки реализующие Where, Order, Any и подобные методы для мапов и слайсов.
Я думаю что дженерики стоит использовать только в самых общих библиотеках. Их использование в логике будет создавать хаос. Но найдутся те, кто со мной не согласен.
Комментарии (45)
QtRoS
13.02.2022 19:50+4А так же появятся библиотеки реализующие Where, Order, Any и подобные методы для мапов и слайсов
Интересно, а почему бы такие популярные вещи не сделать в стандартной библиотеке? Противоречит ли это философии авторов Go? Точно помню и Роб Пайк, и Расс Кокс говорили, что всегда хотели оставить язык минималистичным, но про стандартную библиотеку вроде ничего такого не говорилось.
LINQ по моему скромному мнению вышел чрезвычайно удачным и удобным на практике. Было бы приятно увидеть его аналог в Go. Заодно упражнение это было бы отличным тестом возможностей дженериков.
aegoroff
13.02.2022 23:25Аббревиатура LINQ обозначает целый набор технологий, создающих и использующих возможности интеграции запросов непосредственно в язык C#.
Вы именно LINQ хотите в виде специфичного синтаксиса вида from x in .. where .. select x.something ? или в виде методов (расширений) некой объектной модели вида x.Select(), x.Any() и т.д.?
Если первое, то вряд ли в Go захотят подобное сделать, если второе - есть уже некое подобие https://pkg.go.dev/github.com/ahmetb/go-linq
QtRoS
14.02.2022 01:06В цивилизованном обществе про "специфичный синтаксис" речь обычно не идет, я думаю Вы это прекрасно понимаете, раз в профиле .NET на первом месте.
За наводку на библиотеку спасибо - было интересно посмотреть исходники, особенно реализацию "T-версий" функций с кустарной реализацией дженериков. Вероятно с новым релизом языка она сильной упростится!
QtRoS
13.02.2022 19:59+2Так, а универсальную функцию сортировки любого типа пока еще нельзя написать, получается? Свои оператор <, >, ==, != ведь не реализовать...
swelf
13.02.2022 22:45Будет ordered, с релизом или позже незнаю, но планы есть
В самом низу github.com/golang/go/issues/45458
LynXzp
16.02.2022 00:29-2Есть sort interface, для него надо определить свои методы less, swap, len. В sort для slice можно передать функцию сравнения.
NeoCode
13.02.2022 20:06+3Кстати, они именно дженерики, а не шаблоны? Т.е. применяется упаковка (в interface{}), а не мономорфизация как в С++?
nin-jin
13.02.2022 23:06Это всё дженерики (обобщения). Реализацией их думаю будет боксинг (упаковка) из соображения совместимости.
PerseforeComplete
14.02.2022 08:42А где можно почитать, какие у дженериков под капотом есть варианты реализаций?
nin-jin
14.02.2022 09:12А тут варианта всего 2: либо нагенерировать функций для каждого типа по одному шаблону, либо сделать все типы бинарно совместимыми через упаковку в наиболее общий тип, чтобы его можно было передавать одной и той же обобщённой функции.
Vadim_Aleks
16.02.2022 12:50В рантайме рефлексии не будет. Разработчики называют это stenciling. Подробнее github.com/golang/proposal/blob/master/design/generics-implementation-gcshape.md
andreyiq
13.02.2022 21:04+2Go как всегда, лишь бы не как у всех. Какая мотивация использовать [T] вместо привычного <T>? Те кто пишет на разных языках будут страдать
maxxant
13.02.2022 21:48+2https://gist.github.com/tooolbox/fb385bfa05032ddc989afb393948be48
вкратце, скорость парсинга, невозможно без анализа семантики отделить от сравнения.
И да изначально полукруглые были, были большие дискуссии, в итоге заменили на [ ]
andreyiq
13.02.2022 22:48+2Скорость парсинга? Серьезно? если это было реальной причиной, то они явно свернули не туда. На сколько должна замедлится сборка, чтобы это стало важнее читабельности языка
nin-jin
13.02.2022 23:12А с чего вы взяли, что все языки должны разрабатываться под ваши личные привычки? Нормально задизайненные языки не используют угловые скобки для обобщённых параметров из соображения однозначности синтакиса.
andreyiq
14.02.2022 06:28-2Нормально задизайненные языки не используют угловые скобки для обобщённых параметров из соображения однозначности синтакиса.
т.е. все мейнстримовые языки, делают это не правильно? У вас квадратные скобки не ассоциируются с массивом например?
Есть устоявшиеся вещи, которые в целом всех устраивают и к которым все привыкли, зачем их менять? Только не говорите про ускорение парсинга, никто этого даже не заметит
nin-jin
14.02.2022 08:25+2Угу, не правильно. Вот, пример, где разработчики языка включили мозг и позаботились о пользователе, а не стали бездумно копипастить ошибки прошлого: https://dlang.org/articles/templates-revisited.html#argument-syntax
А вот примеры, где мозг сразу не включили, и сделали как привычно, а потом расхлёбывали:
https://stackoverflow.com/questions/37613981/how-to-use-a-typescript-cast-with-jsx-tsx
https://rsdn.org/forum/cpp/4056508.all
Квадратные скобки у меня ассоциируются с квадратами, круглые с кругами, угловые с треугольниками, а фигурные с сиськами. И что? Введём в язык оператор для рисования сисек?
В разных языках квадратные скобки используются для разных вещей. Например, для доступа к полям объекта в js. Или для двустороннего связывания в angular. Или для аннотаций в c#. И так далее.
NeoCode
15.02.2022 12:35Не только и не столько ускорение парсинга, но и упрощение компилятора и сопутствующих инструментов, например подсветки синтаксиса в редакторах. Для того чтобы понять что имеется в виду в выражении A < B > C нужна информация о типах и объектах, она может быть доступна не всегда, и в общем случае получается на более поздних проходах компилятора.
Можно ввести какую-нибудь дополнительную пару скобок из Unicode, например 〈 〉 или〈 〉. Но пока только как дополнение к ASCII-совместимому синтаксису. Нечто подобное было в C/C++ - диграфы и триграфы, кажется их убрали совсем недавно (или только собирались убрать?)
andreyiq
15.02.2022 13:42+1A < B > C
Разве это валидный код Go? В чем там больше проблем по сравнению например с A[B[C]]?
NeoCode
15.02.2022 19:11Ну вы же спрашивали
Какая мотивация использовать [T] вместо привычного <T>?
В том что скобки - они везде скобки; а < > это еще и "меньше" и "больше" и они могут идти не парой, а по отдельности. Всякие системы подсветки синтаксиса или построения дерева быстрого доступа к коду (outline) в редакторах не производят полноценный синтаксический анализ, а ограничиваются "скобочным анализом".
THQSql
14.02.2022 06:33если это было реальной причиной, то они явно свернули не туда
Сама суть Go в быстрой компиляции.
andreyiq
14.02.2022 06:51У них в приоритете скорость компиляции, а не удобство? Есть ассемблер, очень быстро компилируется.
THQSql
14.02.2022 07:13Есть ассемблер, очень быстро компилируется.
Go все же более высокоуровневый язык.
PerseforeComplete
14.02.2022 11:22-2Компиляция - это процесс перевода из одного языка в другой. Ассемблер это набор мнемоник для машинных кодов процессора. В этой классификации, ассемблер не компилируется, ассемблер интерпретируется аппаратно процессором.
У ассемблера нет сборщика мусора. С ним явно удобнее, чем без, поэтому go всё же удобнее ассемблера
DirectoriX
14.02.2022 15:32ассемблер интерпретируется аппаратно процессором
Прям текстовые файлы интерпретируются, и даже не надо мнемоники в бинарные опкоды переводить?
Что это правильнее назвать не компиляцией, а трансляцией — соглашусь.
NN1
14.02.2022 00:08+3Как сказать, Go не будет первым языком использующим квадратные скобки для обольщений.
makarychev_13
14.02.2022 18:03+1Так и не понял, почему они решили не делать дженерики в методах. И исправят ли это в будущем.
Ну и синатксис с ~int смущает немного.
soulwish
15.02.2022 10:19+2Здорово, что обобщенное программирование приходит в Go, но выглядит неполно. Работа с типами в рантайме через reflect убивает почти всю мощь шаблонов. Из плюсов конечно не надо будет на каждый тип свою функцию писать.
Vadim_Aleks
16.02.2022 11:50Работать с конкретным типом вместо обобщенного в том же C# делалось бы через рефлексию. В Go представлена возможность писать обобщенные функции\структуры\интерфейсы и писать нужную логику без рефлетка
franzose
Думаю, .NET-разработчики с этим не согласятся. Просто всё надо стараться делать с умом)
marshinov
На дженериках прекрасно пишутся dsl поверх linq. Для язык, где дженериков не было это, видимо, хаос и анархия.
Vadim_Aleks
В Go пока что нет и не будет generic methods, а без них непонятно какую бизнес-логику можно реализовывать обобщенно
aegoroff
Да и не только .NET - С++, Rust и много кто еще это использует в BL и не жалуется
franzose
Я тоже подумал об этом, но уже после того, как написал комментарий. А исправить не успел) Языки JVM в ту же копилку и т.д.