Самое привлекательное в молодом Го было то, что он был худым и не учитывал все запутанные вещи, которые были в других языках. Это заняло много времени, но в конце концов Суслики решили, что дженерики теперь являются частью языка.
Вслед за новостями о том, что Python перенял изощренность в форме сопоставления шаблонов, у нас теперь есть новости о том, что Go наконец-то нашел способ включить дженерики. Прямо со слова go, каламбура, предназначенного и будет повторяться, основная критика Go заключалась в том, что ему не хватало дженериков. Конечно, многие программисты Go не пропустили его и активно работали над тем, чтобы он оставался за пределами языка. Этого никогда не произойдет, поскольку дженерики настолько распространены, что являются частью официальной догмы современных языков.
Должно быть ясно, что на самом деле дженерики не имеют ничего общего с идеей объекта. Дженерики необходимы только тогда, когда вы применяете строгую типизацию, и хотя строгая типизация может быть частью большинства основных языков, это не обязательно. В сочетании с объектами и иерархиями типов, которые обычно навязываются этим объектом, все усложняется, и вы можете легко потерять из виду, что происходит. Go, с другой стороны, не особенно объектно-ориентирован и не навязывает иерархию типов, но он строго типизирован. Это означает, что трудно написать алгоритм, который работает с целым рядом типов.
Когда вы пишете алгоритм на типизированном языке, вы должны сказать, с какими типами он работает, а некоторые алгоритмы настолько общие, что они работают с любым типом. Таким образом, чтобы сделать возможным написание этих общих алгоритмов, вы должны найти способ победить сильную типизацию — либо вы должны написать программу для каждого типа, даже если программы в основном одинаковы.
Есть несколько вещей, которые можно сказать о дженериках, которые не часто говорят, и если они это делают, они вызывают проблемы, поскольку сильная типизация и дженерики-это глубокие убеждения. Во-первых, существует не так много действительно универсальных алгоритмов, потому что большинство алгоритмов предполагают некоторые свойства и методы. Например, наиболее очевидным общим алгоритмом является сортировка, и даже здесь вы должны предположить, что существует некоторая функция, которая может использоваться для сравнения сущностей.
Самое простое решение для универсальных методов — не применять строгую типизацию и просто полагаться на инструменты для проверки того, что сущности имеют свойства, необходимые для алгоритма, — не популярное решение. Во — вторых, лучше всего просто использовать верхнюю часть иерархии объектов- предполагая, что язык имеет иерархию объектов — и полагаться на принцип подстановки, чтобы позволить алгоритму работать со всеми другими производными типами. Это часто не удается, потому что немногие языки имеют полностью согласованную иерархию объектов — они обычно имеют примитивные типы, которые существуют снаружи.
Если вы не хотите использовать ни один из этих подходов, то дженерики — это ваш единственный вариант-позвольте программисту указать типы, используемые в алгоритме в качестве параметров. То есть, точно так же, как вы используете параметры, чтобы заставить функцию или метод работать с различными переменными определенного типа, вы можете использовать параметры типа для указания переменных и их типов.
Это звучит просто, но есть всевозможные проблемы. Вот где все может стать действительно сложным: как вы ограничиваете типы, которые может использовать общая процедура? А как насчет составных объектов, таких как контейнеры? Должны ли параметры типа быть сущностями времени компилятора или времени выполнения и так Далее…
Эти и подобные причины объясняют, почему потребовалось так много времени, чтобы достичь консенсуса — это и тот факт, что значительное меньшинство Сусликов вообще не хотело расширения языка…
Итак, на каком типе дженериков они остановились? Самый простой способ объяснить это-процитировать резюме в объявлении:
Функции могут иметь дополнительный список параметров типа, который использует квадратные скобки, но в остальном выглядит как обычный список параметров: func F[T any](p T) { … }.
Эти параметры типа могут использоваться обычными параметрами и в теле функции.
Типы также могут иметь список параметров типа: type MySlice[T any] []T.
Каждый параметр типа имеет ограничение типа, так же как каждый обычный параметр имеет ограничение типа: func F[T](p T) { … }.
Ограничения типа-это типы интерфейсов.
Новое предварительное имя any-это ограничение типа, которое допускает любой тип.
Типы интерфейсов, используемые в качестве ограничений типа, могут иметь список предварительно определенных типов; только аргументы типа, соответствующие одному из этих типов, удовлетворяют ограничению.
Универсальные функции могут использовать только операции, разрешенные их ограничениями типа.
Использование универсальной функции или типа требует передачи аргументов типа.
Вывод типа позволяет опустить аргументы типа вызова функции в общих случаях.
Я предсказываю, что любой будет наиболее часто используемым типом…