Пройти курс: Generics

В этом разделе мы узнаем о Generics — долгожданной функции, которая появилась в Go версии 1.18.

Что такое Generics?

Generics означает параметризованные типы. Проще говоря, дженерики позволяют программистам писать код, в котором тип может быть указан позже, поскольку тип не имеет непосредственного значения.

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

В нашем примере у нас есть простые функции суммирования для различных типов, таких как int, float64 и string. Поскольку переопределение методов в Go не разрешено, нам обычно приходится создавать новые функции.

package main

import "fmt"

func sumInt(a, b int) int {
    return a + b
}

func sumFloat(a, b float64) float64 {
    return a + b
}

func sumString(a, b string) string {
    return a + b
}

func main() {
    fmt.Println(sumInt(1, 2))
    fmt.Println(sumFloat(4.0, 2.0))
    fmt.Println(sumString("a", "b"))
}
Вход в полноэкранный режим Выход из полноэкранного режима

Как мы видим, кроме типов, эти функции довольно похожи.

Давайте посмотрим, как можно определить общую функцию.

func fnName[T constraint]() {
    ...
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Здесь T — это параметр нашего типа, а constraint будет интерфейсом, который позволяет использовать любой тип, реализующий этот интерфейс.

Я знаю, знаю, это запутанно. Итак, давайте начнем строить нашу общую функцию sum.

Здесь мы будем использовать T в качестве параметра типа и пустой interface{} в качестве ограничения.

func sum[T interface{}](a, b T) T {
    fmt.Println(a, b)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Также, начиная с Go 1.18, мы можем использовать any, что практически эквивалентно пустому интерфейсу.

func sum[T any](a, b T) T {
    fmt.Println(a, b)
}
Войти в полноэкранный режим Выход из полноэкранного режима

С параметрами типа появляется необходимость передавать аргументы типа, что может сделать наш код многословным.

sum[int](1, 2) // explicit type argument
sum[float64](4.0, 2.0)
sum[string]("a", "b")
Вход в полноэкранный режим Выход из полноэкранного режима

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

sum(1, 2)
sum(4.0, 2.0)
sum("a", "b")
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте запустим это и посмотрим, работает ли оно.

$ go run main.go
1 2
4 2
a b
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь давайте обновим функцию sum, чтобы добавить наши переменные.

func sum[T any](a, b T) T {
    return a + b
}
Вход в полноэкранный режим Выход из полноэкранного режима
fmt.Println(sum(1, 2))
fmt.Println(sum(4.0, 2.0))
fmt.Println(sum("a", "b"))
Войти в полноэкранный режим Выход из полноэкранного режима

Но теперь, если мы выполним это, то получим ошибку, что оператор + не определен в ограничении.

$ go run main.go
./main.go:6:9: invalid operation: operator + not defined on a (variable of type T constrained by any)
Войти в полноэкранный режим Выход из полноэкранного режима

Хотя ограничение типа any обычно работает, оно не поддерживает операторы.

Поэтому давайте определим наше собственное пользовательское ограничение с помощью интерфейса. Наш интерфейс должен определять набор типов, содержащий int, float и string.

Вот как выглядит наш интерфейс SumConstraint.

type SumConstraint interface {
    int | float64 | string
}

func sum[T SumConstraint](a, b T) T {
    return a + b
}

func main() {
    fmt.Println(sum(1, 2))
    fmt.Println(sum(4.0, 2.0))
    fmt.Println(sum("a", "b"))
}
Вход в полноэкранный режим Выход из полноэкранного режима

А это должно работать, как ожидалось

$ go run main.go
3
6
ab
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы также можем использовать пакет constraints, который определяет набор полезных ограничений для использования с параметрами типа.

Для этого нам потребуется установить пакет constraints.

$ go get golang.org/x/exp/constraints
go: added golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd
Вход в полноэкранный режим Выход из полноэкранного режима
import (
    "fmt"

    "golang.org/x/exp/constraints"
)

func sum[T constraints.Ordered](a, b T) T {
    return a + b
}

func main() {
    fmt.Println(sum(1, 2))
    fmt.Println(sum(4.0, 2.0))
    fmt.Println(sum("a", "b"))
}
Войти в полноэкранный режим Выход из полноэкранного режима

Здесь мы используем ограничение Ordered.

type Ordered interface {
    Integer | Float | ~string
}
Войти в полноэкранный режим Выход из полноэкранного режима

~ — это новая лексема, добавленная в Go, и выражение ~string означает множество всех типов, базовым типом которых является string.

И это по-прежнему работает, как и ожидалось.

$ go run main.go
3
6
ab
Вход в полноэкранный режим Выход из полноэкранного режима

Generics — удивительная возможность, потому что она позволяет писать абстрактные функции, которые в некоторых случаях могут значительно сократить дублирование кода.

Когда использовать дженерики

Итак, когда использовать дженерики? В качестве примера можно привести следующие случаи использования:

  • Функции, работающие с массивами, срезами, картами и каналами.
  • Структуры данных общего назначения, такие как стек или связанный список.
  • Для уменьшения дублирования кода.

В заключение добавлю, что хотя дженерики являются отличным дополнением к языку, их следует использовать экономно.

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


Эта статья является частью моего открытого курса по Go, доступного на Github.

karanpratapsingh / go-course

Освойте основы и расширенные возможности языка программирования Go

Курс по Go

Привет, добро пожаловать на курс, и спасибо за изучение Go. Я надеюсь, что этот курс обеспечит вам отличный опыт обучения.

Этот курс также доступен на моем сайте, а также на Educative.io

Оглавление

  • Начало работы

    • Что такое Go?
    • Зачем изучать Go?
    • Установка и настройка
  • Глава I

    • Hello World
    • Переменные и типы данных
    • Форматирование строк
    • Управление потоком данных
    • Функции
    • Модули
    • Пакеты
    • Рабочие пространства
    • Полезные команды
    • Сборка
  • Глава II

    • Указатели
    • Структуры
    • Методы
    • Массивы и фрагменты
    • Карты
  • Глава III

    • Интерфейсы
    • Ошибки
    • Паника и восстановление
    • Тестирование
    • Дженерики
  • Глава IV

    • Параллелизм
    • Гороутины
    • Каналы
    • Выбрать
    • Пакет синхронизации
    • Продвинутые паттерны параллелизма
    • Контекст
  • Приложение

    • Следующие шаги
    • Ссылки

Что такое Go?

Go (также известный как Golang) — это язык программирования, разработанный в Google в 2007 году и открытый в 2009 году.

Основное внимание в нем уделяется простоте, надежности и эффективности. Он был разработан, чтобы объединить эффективность, скорость и безопасность статически типизированного и компилируемого языка с легкостью…

Посмотреть на GitHub

Оцените статью
devanswers.ru
Добавить комментарий