Скоро выйдет релиз 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)


  1. franzose
    13.02.2022 16:05
    +12

    Я думаю что дженерики стоит использовать только в самых общих библиотеках. Их ипользование в логике будет создавать хаос.

    Думаю, .NET-разработчики с этим не согласятся. Просто всё надо стараться делать с умом)


    1. marshinov
      13.02.2022 16:33
      +9

      На дженериках прекрасно пишутся dsl поверх linq. Для язык, где дженериков не было это, видимо, хаос и анархия.


      1. Vadim_Aleks
        16.02.2022 13:11

        В Go пока что нет и не будет generic methods, а без них непонятно какую бизнес-логику можно реализовывать обобщенно


    1. aegoroff
      13.02.2022 17:26
      +7

      Да и не только .NET - С++, Rust и много кто еще это использует в BL и не жалуется


      1. franzose
        13.02.2022 17:27
        +3

        Я тоже подумал об этом, но уже после того, как написал комментарий. А исправить не успел) Языки JVM в ту же копилку и т.д.


  1. QtRoS
    13.02.2022 19:50
    +4

    А так же появятся библиотеки реализующие Where, Order, Any и подобные методы для мапов и слайсов

    Интересно, а почему бы такие популярные вещи не сделать в стандартной библиотеке? Противоречит ли это философии авторов Go? Точно помню и Роб Пайк, и Расс Кокс говорили, что всегда хотели оставить язык минималистичным, но про стандартную библиотеку вроде ничего такого не говорилось.

    LINQ по моему скромному мнению вышел чрезвычайно удачным и удобным на практике. Было бы приятно увидеть его аналог в Go. Заодно упражнение это было бы отличным тестом возможностей дженериков.


    1. 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


      1. QtRoS
        14.02.2022 01:06

        В цивилизованном обществе про "специфичный синтаксис" речь обычно не идет, я думаю Вы это прекрасно понимаете, раз в профиле .NET на первом месте.

        За наводку на библиотеку спасибо - было интересно посмотреть исходники, особенно реализацию "T-версий" функций с кустарной реализацией дженериков. Вероятно с новым релизом языка она сильной упростится!


  1. QtRoS
    13.02.2022 19:59
    +2

    Так, а универсальную функцию сортировки любого типа пока еще нельзя написать, получается? Свои оператор <, >, ==, != ведь не реализовать...


    1. swelf
      13.02.2022 22:45

      Будет ordered, с релизом или позже незнаю, но планы есть
      В самом низу github.com/golang/go/issues/45458


    1. LynXzp
      16.02.2022 00:29
      -2

      Есть sort interface, для него надо определить свои методы less, swap, len. В sort для slice можно передать функцию сравнения.


  1. NeoCode
    13.02.2022 20:06
    +3

    Кстати, они именно дженерики, а не шаблоны? Т.е. применяется упаковка (в interface{}), а не мономорфизация как в С++?


    1. nin-jin
      13.02.2022 23:06

      Это всё дженерики (обобщения). Реализацией их думаю будет боксинг (упаковка) из соображения совместимости.


      1. PerseforeComplete
        14.02.2022 08:42

        А где можно почитать, какие у дженериков под капотом есть варианты реализаций?


        1. nin-jin
          14.02.2022 09:12

          А тут варианта всего 2: либо нагенерировать функций для каждого типа по одному шаблону, либо сделать все типы бинарно совместимыми через упаковку в наиболее общий тип, чтобы его можно было передавать одной и той же обобщённой функции.


        1. NeoCode
          15.02.2022 12:23
          +1

          Вот отличная статья: https://habr.com/ru/company/vk/blog/461321/


    1. Vadim_Aleks
      16.02.2022 12:50

      В рантайме рефлексии не будет. Разработчики называют это stenciling. Подробнее github.com/golang/proposal/blob/master/design/generics-implementation-gcshape.md


  1. andreyiq
    13.02.2022 21:04
    +2

    Go как всегда, лишь бы не как у всех. Какая мотивация использовать [T] вместо привычного <T>? Те кто пишет на разных языках будут страдать


    1. maxxant
      13.02.2022 21:48
      +2

      https://gist.github.com/tooolbox/fb385bfa05032ddc989afb393948be48

      вкратце, скорость парсинга, невозможно без анализа семантики отделить от сравнения.

      И да изначально полукруглые были, были большие дискуссии, в итоге заменили на [ ]


      1. andreyiq
        13.02.2022 22:48
        +2

        Скорость парсинга? Серьезно? если это было реальной причиной, то они явно свернули не туда. На сколько должна замедлится сборка, чтобы это стало важнее читабельности языка


        1. nin-jin
          13.02.2022 23:12

          А с чего вы взяли, что все языки должны разрабатываться под ваши личные привычки? Нормально задизайненные языки не используют угловые скобки для обобщённых параметров из соображения однозначности синтакиса.


          1. andreyiq
            14.02.2022 06:28
            -2

            Нормально задизайненные языки не используют угловые скобки для обобщённых параметров из соображения однозначности синтакиса.

            т.е. все мейнстримовые языки, делают это не правильно? У вас квадратные скобки не ассоциируются с массивом например?

            Есть устоявшиеся вещи, которые в целом всех устраивают и к которым все привыкли, зачем их менять? Только не говорите про ускорение парсинга, никто этого даже не заметит


            1. 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#. И так далее.


            1. NeoCode
              15.02.2022 12:35

              Не только и не столько ускорение парсинга, но и упрощение компилятора и сопутствующих инструментов, например подсветки синтаксиса в редакторах. Для того чтобы понять что имеется в виду в выражении A < B > C нужна информация о типах и объектах, она может быть доступна не всегда, и в общем случае получается на более поздних проходах компилятора.

              Можно ввести какую-нибудь дополнительную пару скобок из Unicode, например 〈   〉 или〈  〉. Но пока только как дополнение к ASCII-совместимому синтаксису. Нечто подобное было в C/C++ - диграфы и триграфы, кажется их убрали совсем недавно (или только собирались убрать?)


              1. andreyiq
                15.02.2022 13:42
                +1

                A < B > C

                Разве это валидный код Go? В чем там больше проблем по сравнению например с A[B[C]]?


                1. NeoCode
                  15.02.2022 19:11

                  Ну вы же спрашивали

                   Какая мотивация использовать [T] вместо привычного <T>? 

                  В том что скобки - они везде скобки; а < > это еще и "меньше" и "больше" и они могут идти не парой, а по отдельности. Всякие системы подсветки синтаксиса или построения дерева быстрого доступа к коду (outline) в редакторах не производят полноценный синтаксический анализ, а ограничиваются "скобочным анализом".


        1. THQSql
          14.02.2022 06:33

          если это было реальной причиной, то они явно свернули не туда

          Сама суть Go в быстрой компиляции.


          1. andreyiq
            14.02.2022 06:51

            У них в приоритете скорость компиляции, а не удобство? Есть ассемблер, очень быстро компилируется.


            1. THQSql
              14.02.2022 07:13

              Есть ассемблер, очень быстро компилируется.

              Go все же более высокоуровневый язык.


            1. PerseforeComplete
              14.02.2022 11:22
              -2

              Компиляция - это процесс перевода из одного языка в другой. Ассемблер это набор мнемоник для машинных кодов процессора. В этой классификации, ассемблер не компилируется, ассемблер интерпретируется аппаратно процессором.

              У ассемблера нет сборщика мусора. С ним явно удобнее, чем без, поэтому go всё же удобнее ассемблера


              1. DirectoriX
                14.02.2022 15:32

                ассемблер интерпретируется аппаратно процессором
                Прям текстовые файлы интерпретируются, и даже не надо мнемоники в бинарные опкоды переводить?
                Что это правильнее назвать не компиляцией, а трансляцией — соглашусь.


          1. PerseforeComplete
            14.02.2022 10:04

            Я думал что в минимальном ортогональном наборе ключевых слов


    1. NN1
      14.02.2022 00:08
      +3

      Как сказать, Go не будет первым языком использующим квадратные скобки для обольщений.


      1. QtRoS
        14.02.2022 00:56
        +6

        квадратные скобки для обольщений

        Забавная описка :) Язык программирования, который обольщает программиста - класс!


        1. NN1
          14.02.2022 09:01
          +1

          Ох уж этот автокорректор :)


      1. andreyiq
        14.02.2022 06:34

        Не смог вспомнить у кого квадратные скобки. Это какой-то популярный язык?


        1. nin-jin
          14.02.2022 08:35

          1. andreyiq
            14.02.2022 08:42
            -1

            Язык который даже в топ 50 TIOBE не вошёл, отличный вариант для подражания)


            1. nin-jin
              14.02.2022 09:15
              -1

              Миллионы мух не могут ошиаться, ага.


        1. NN1
          14.02.2022 09:04
          +4

          Scala, Python, Nim, Nemerle, Eiffel.

          Первые два вопросов популярности не вызывают надеюсь.


          1. andreyiq
            14.02.2022 09:47
            +1

            Вот это уже довод, давно Python не смотрел. Возможно был не прав


    1. Xop
      14.02.2022 15:25
      +2

      Ну вот в скале как раз [T]


  1. makarychev_13
    14.02.2022 18:03
    +1

    Так и не понял, почему они решили не делать дженерики в методах. И исправят ли это в будущем.

    Ну и синатксис с ~int смущает немного.


  1. soulwish
    15.02.2022 10:19
    +2

    Здорово, что обобщенное программирование приходит в Go, но выглядит неполно. Работа с типами в рантайме через reflect убивает почти всю мощь шаблонов. Из плюсов конечно не надо будет на каждый тип свою функцию писать.


    1. Vadim_Aleks
      16.02.2022 11:50

      Работать с конкретным типом вместо обобщенного в том же C# делалось бы через рефлексию. В Go представлена возможность писать обобщенные функции\структуры\интерфейсы и писать нужную логику без рефлетка