Golang пощупаем дженерики

edca31b5fe0c93de74880ad76fbd54c7.jpg

Скоро выйдет релиз 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 и подобные методы для мапов и слайсов.

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

© Habrahabr.ru