company_banner

Зачем нужны дженерики в Go?

Автор оригинала: Ian Lance Taylor
  • Перевод

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

Go вышел 10 ноября 2009-го. Меньше чем через сутки появился первый комментарий про дженерики. В нём также упомянуты исключения, которые мы добавили в язык в виде паники и восстановления (panic and recover) в начале 2010-го.

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

Зачем нужны дженерики?


Что означает добавление дженериков и почему мы этого хотим?

Перефразируя Jazayeri и других: программирование с дженериками позволяет представлять функции и структуры данных в виде дженериков, исключая типы.

Что это означает?

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

Пусть у нас есть слайс целых чисел.

func ReverseInts(s []int) {
    first := 0
    last := len(s)
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

Выглядит просто. Но даже для простой функции вроде этой вам может понадобиться написать несколько тестов. И когда я это сделал, то обнаружил баг. Уверен, многие его уже заметили.

func ReverseInts(s []int) {
    first := 0
    last := len(s) - 1
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

Мы вычитаем 1, в то время как назначаем переменную last. Теперь поменяем порядок в слайсе строковых значений.

func ReverseStrings(s []string) {
    first := 0
    last := len(s) - 1
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

Если вы сравните ReverseInts и ReverseStrings, то увидите, что функции совершенно одинаковые, за исключением типа параметра. Вряд ли я вас этим удивил.

А вот что может удивить новичков в Go, так это отсутствие способа написать простую функцию Reverse, работающую со слайсами любых типов.

В большинстве других языков вы можете написать такую функцию.

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

Большинство других статически типизируемых языков, вроде С++, Java, Rust или Swift, поддерживают дженерики именно для таких ситуаций.

Современное программирование на Go с дженериками


Так как же люди пишут подобный код на Go?

В этом языке вы можете написать функцию, которая работает с разными типами слайсов с помощью интерфейсного типа (interface type) и определения метода для типов слайсов, которые вы хотите передавать. Так работает функция sort.Sort из стандартной библиотеки.

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

Но этот подход не соответствует нашим желаниям. Нужно самостоятельно писать методы в интерфейсах. Довольно странно определять именованный тип с парой методов чтобы лишь поменять порядок элементов в слайсе. А методы, которые вы пишете, будут совершенно одинаковыми для всех типов слайсов, так что мы не исключили код, а, в каком-то смысле, перенесли и уплотнили. Хотя интерфейсы — это способ представления дженериков, они не дают нам всего, что нам нужно от дженериков.

Другой способ использования интерфейсов для дженериков, при котором не нужно писать методы, заключается в том, чтобы переложить на язык обязанность определять методы для каких-то типов. Сейчас Go этого не поддерживает, но, к примеру, язык может определять, чтобы каждый тип слайса имел метод Index, возвращающий элемент. Но чтобы использовать этот метод на практике, потребуется возвращать пустой тип интерфейса, и тогда мы теряем все преимущества статического типизирования. Не будет способа определять дженерик-функцию, которая берёт два разных слайса с элементами одного типа, или которая берёт map'у с элементами одного типа и возвращает слайс с элементами такого же типа. Go статически типизирован потому, что это облегчает написание больших программ. Мы не хотим терять преимущества статического типизирования ради выгод дженериков.

Ещё один подход заключается в написании дженерик-функции Reverse, использующей пакет reflect, но это было бы слишком странно и работал бы чересчур медленно. Кроме того, потребовалось бы делать явные утверждения типов и отказаться от проверки на статичность типов.

Или можно написать генератор кода, который берёт тип и генерирует функцию Reverse для слайсов этого типа. Для этого подходят несколько генераторов. Но такое решение прибавляет ещё один этап в каждый пакет, которому понадобится Reverse, что усложняет сборку из-за необходимости компилирования всех копий, а исправление багов в исходном коде потребует перегенерирования всех экземпляров, некоторые из которых могут уже задействоваться в совершенно других проектах.

Все описанные подходы достаточно странные, и я думаю, что большинство программистов, которым нужно менять порядок в слайсах на Go, просто пишут функцию для конкретного типа слайса. Потом им нужно написать тесты для функций, чтобы удостовериться, что они не сделали простых ошибок, вроде той, что я сделал в начале. А потом придётся рутинно прогонять эти тесты.

И когда мы это делаем, это означает, что мы делаем кучу лишней работы просто ради функции, которая выглядит точно так же, за исключением типа элементов. Дело не в том, что это невозможно. Это точно возможно, и программисты на Go так и делают. Просто должен быть способ получше.

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

Что дженерики могут дать Go


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

А поскольку мы говорим об open source, то будет ещё лучше, если кто-нибудь сможет написать Reverse один раз, а мы все будем пользоваться этой реализацией.

Здесь мне следует сказать, что термин «дженерики» может обозначать много разного. В этой статье я подразумеваю под «дженериками» то, что описал выше. В частности, я не имею в виду шаблоны, как в С++, которые поддерживают гораздо больше возможностей, чем я перечислил.
Я подробно рассказал о Reverse, но есть и много других функций, которые мы могли бы писать как дженерики, например:

  • Поиск наименьшего или наибольшего элемента в слайсе.
  • Поиск среднего или стандартного отклонения в слайсе.
  • Вычисление модуля или пересечения в map.
  • Поиск кратчайшего пути в графе с узлами или рёбрами.
  • Применение к слайсу или map'е функции преобразования, которая возвращает новый слайс или map.

Эти примеры доступны в большинстве других языков. Фактически, я написал этот список, просто посмотрев на шаблоны из стандартной библиотеки С++.

Есть примеры, характерные для Go с его строгой поддержкой параллелизма.

  • Чтение из канала без таймаута.
  • Комбинирование двух каналов в один.
  • Параллельный вызов списка функций, который возвращает слайс результатов.
  • Вызов списка функций с использованием Context, возвращающий результат первой завершаемой функции, отменяющий и очищающий дополнительные горутины.

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

Чтобы не было недопонимания, это всего лишь примеры. Есть много других функций общего назначения, которые будет проще и безопаснее писать с использованием дженериков. Кроме того, как написал выше, это не только функции, а ещё и структуры данных.

В Go встроены две дженерик-структуры данных общего назначения: слайсы и map. Они могут содержать значения любых типов, с проверкой на статичность типов значений, которые хранятся и извлекаются. Значения хранятся сами по себе, а не как интерфейсные типы. То есть, когда у меня есть []int, слайс содержит сами числа, а не числа, преобразованные в интерфейсный тип.

Слайсы и map'ы — самые полезные дженерик-структуры данных, но они не единственные. Вот другие:

  • Наборы.
  • Самобалансирующиеся деревья с эффективной вставкой и обходом в порядке сортировки.
  • Мальтикарты с многочисленными экземплярами ключа.
  • Конкуретные map'ы хэшей, поддерживающие параллельную вставку и поиск безо всяких блокировок.

Если мы можем писать дженерик-типы, мы можем определять новые структуры данных, вроде тех, что имеют те же преимущества проверки типов, как у слайсов и map: компилятор может статически проверить типы значений, которые они содержат, и значения могут храниться сами по себе, а не как интерфейсные типы.

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

Все эти примеры должны быть такими же, как в случае с Reverse: дженерик-функции и структуры пишутся один раз, помещаются в пакет, а затем многократно используются там, где нужно. Они должны работать как слайсы и map, в том смысле, что они должны хранить не значения пустых интерфейсных типов, а конкретные типы, которые будут проверяться при компиляции.

Вот какую пользу может извлечь Go из дженериков. Дженерики могут стать эффективными кирпичиками, позволяющими делиться кодом и проще создавать программы.

Надеюсь, я смог объяснить, почему имеет смысл их изучить.

Преимущества и цена


Но дженерики не появляются из Big Rock Candy Mountain, края, в котором солнце каждый день сияет над лимонадными источниками. У каждого изменения языка есть своя цена. Несомненно, что добавление дженериков в Go усложнит язык. Как и с любым изменением, нам нужно обсудить максимизацию преимуществ и минимизацию цены.

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

Чтобы выражаться конкретнее, приведу несколько рекомендаций, которым мы должны следовать.

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

Сложность падает на автора дженерик-кода, а не пользователя
Сложность должна максимально перекладываться на программиста, пишущего дженерик-пакет. Мы не хотим, чтобы пользователь пакета волновался по поводу дженериков. Значит, должна быть возможность естественным образом вызывать дженерик-функции, то есть нужно сообщать обо всех ошибках использования дженерик-пакетов так, чтобы их можно было легко понять и исправить. Также должен быть простой способ отладки вызовов в дженерик-коде.

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

Быстрая сборка и исполнение
Мы хотим как можно больше сократить длительность сборки и исполнения по сравнению с сегодняшней производительностью Go. C дженериками связаны компромиссы между быстрой сборкой и исполнением. Нам нужно как можно больше ускорить оба процесса.

Сохранение ясности и простоты Go
Самое важное заключается в том, что сейчас Go является простым языком. Программы на Go обычно легко понять. В ходе нашего длительного исследования мы большое внимание уделяем поиску возможности добавления дженериков с сохранением ясности и простоты. Нам нужно найти механизмы, которые хорошо укладываются в текущий язык, которые не превратят его во что-то совершенно другое.

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

Черновик архитектуры


Думаю, это возможно. Чтобы завершить статью, я хочу от обсуждения необходимости внедрения дженериков и требований к ним перейти к обсуждению архитектуры, с помощью которой дженерики можно внедрить в Go.

На Gophercon в этом году Роберт Гриземер (Robert Griesemer) и я опубликовали черновик архитектуры дженериков в Go. По ссылке вы найдёте все подробности, а здесь я коснусь лишь основных моментов.

Так реализована функция Reverse.

func Reverse (type Element) (s []Element) {
    first := 0
    last := len(s) - 1
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

Тело функции совершенно такое же, изменилась только сигнатура.

Тип элементов слайса исключён. Теперь он называется Element и превратился в так называемый параметр типа (type parameter). Вместо того, чтобы быть частью параметра типа слайса, он теперь отдельный, дополнительный параметр типа.

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

func ReverseAndPrint(s []int) {
    Reverse(int)(s)
    fmt.Println(s)
}

Это (int), представленный в примере после Reverse.

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

Вызов дженерик-функции выглядит как вызов любой другой функции.

func ReverseAndPrint(s []int) {
    Reverse(s)
    fmt.Println(s)
}

Хотя дженерик-функция Reverse чуть сложнее функций ReverseInts и ReverseStrings, эта сложность возлагается на автора кода, а не на вызывающего.

Контракты


Поскольку Go статически типизирован, нужно обсудить тип параметра типа. Этот метатип говорит компилятору, какого рода аргументы типов разрешены при вызове дженерик-функции, а также какого рода операции может выполнить дженерик-функция со значениями параметра типа.

Функция Reverse может работать со слайсами любых типов. Она может только присваивать значения типа Element, который работает с любыми типами в Go. Для этого вида дженерик-функций, который встречается очень часто, нам не нужно говорить ничего особого о параметре типа.

Теперь посмотрим на другую функцию.

func IndexByte (type T Sequence) (s T, b byte) int {
    for i := 0; i < len(s); i++ {
        if s[i] == b {
            return i
        }
    }
    return -1
}

Сейчас пакеты bytes и strings из стандартной библиотеки содержат функцию IndexByte. Эта функция возвращает индекс b в последовательности s, где s является строкой или []byte. Мы могли бы одной этой дженерик-функцией заменить две функции в пакетах bytes и strings. На практике можно этого не делать, но это простой и полезный пример.

Теперь нам нужно узнать, как действует параметр T — как строка или []byte. Можно применить к нему len, индексировать и сравнить результат операции индексирования со значением byte.

Для выполнения компиляции самому параметру типа T тоже нужен тип. Это будет метатип, но поскольку нам иногда нужно описывать различные взаимосвязанные типы, и поскольку метатип описывает связь между реализацией дженерик-функции и вызывающим её, мы называем тип T контрактом. В данном случае контракт называется Sequence. Он идёт после списка параметров типа.

Вот как контракт Sequence определяется в этом примере.

contract Sequence(T) {
    T string, []byte
}

Всё просто, потому что и пример простой: параметр типа T может быть строкой или []byte. Контракт может быть новым ключевым словом или специальным идентификатором, который распознаётся в области видимости пакета. Подробности вы можете узнать из документации к черновику архитектуры.

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

Контракты позволяют задавать тип параметра типа и/или список методов параметра типа. Также контракты позволяют описывать взаимосвязи между разными параметрами типов.

Контракты с методами


Вот ещё один простой пример функции, использующей метод String для возвращения []string строкового представления всех элементов в s.

func ToStrings (type E Stringer) (s []E) []string {
    r := make([]string, len(s))
    for i, v := range s {
        r[i] = v.String()
    }
    return r
}

Всё просто: проходим по слайсу, вызываем метод String применительно к каждому элементу и возвращаем слайс с получившимся строковыми значениями.

Этой функции нужно, чтобы тип элемента реализовывал метод String. И контракт Stringer это гарантирует.

contract Stringer(T) {
    T String() string
}

Контракт утверждает, что T должен реализовывать метод String.

Вы могли заметить, что этот контракт выглядит как интерфейс fmt.Stringer, так что нужно указать на то, что аргумент функции ToStrings не является слайсом fmt.Stringer. Это слайс какого-то типа элементов, и тип элементов реализует fmt.Stringer. Представления памяти слайса типа элементов и слайса fmt.Stringer обычно различаются, и Go не поддерживает прямое преобразование между ними. Так что лучше не полениться и написать, даже если fmt.Stringer существует.

Контракты с несколькими типами


Вот пример контракта с несколькими параметрами типов.

type Graph (type Node, Edge G) struct { ... }

contract G(Node, Edge) {
    Node Edges() []Edge
    Edge Nodes() (from Node, to Node)
}

func New (type Node, Edge G) (nodes []Node) *Graph(Node, Edge) {
    ...
}

func (g *Graph(Node, Edge)) ShortestPath(from, to Node) []Edge {
    ...
}

Здесь описан граф, построенный из узлов и рёбер. Мы не требуем для него определённую структуру данных. Вместо этого мы говорим, что тип Node должен содержать метод Edges, который возвращает список рёбер, соединённых с Node. А тип Edge должен иметь метод Nodes, который возвращает два Nodes, соединённых Edge.

Я опустил реализацию, но здесь показана сигнатура функции New, которая возвращает Graph, и сигнатура метода ShortestPath в Graph.

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

Упорядоченные типы (Ordered types)


В Go удивительно часто жалуются на отсутствие функции Min. Ну, или Max. Причина в том, что полезная функция Min должна работать только с упорядоченными типами, то есть она должна быть дженериком.

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

func Min (type T Ordered) (a, b T) T {
    if a < b {
        return a
    }
    return b
}

Контракт Ordered говорит о том, что тип T должен быть упорядоченным, то есть контракт поддерживает операторы вроде «меньше чем», «больше чем», и так далее.

contract Ordered(T) {
    T int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

Контракт Ordered — это просто список всех упорядоченных типов, определённых языком. Контракт принимает любые типы из списка, или любые именованные типы, в основе которых лежит какой-то тип из списка. По сути, любой тип можно использовать с оператором «меньше чем».

Гораздо проще перечислить типы, поддерживающие оператор «меньше чем», чем изобрести новую нотацию, работающую со всеми операторами. Наконец, в Go операторы поддерживаются только встроенными типами.

Тот же подход можно использовать применительно к любому оператору, или, в более общем плане, написать контракт для любой дженерик-функции, которая должна работать со встроенными типами. Это позволяет тому, кто пишет дженерик-функцию, явно указать набор типов, с которыми должна использоваться функция. И это позволяет вызывающему дженерик-функцию ясно видеть, применима ли функция к типам, с которыми используется.

На практике этот контракт, вероятно, попадёт в стандартную библиотеку. И поэтому функция Min (которая, вероятно, тоже окажется в стандартной библиотеке) будет выглядеть так. Здесь мы просто ссылаемся на контракт Ordered, определённый в контрактах пакета.

func Min (type T contracts.Ordered) (a, b T) T {
    if a < b {
        return a
    }
    return b
}

Дженерик-структуры данных


Наконец, давайте посмотрим на простую дженерик-структуру данных, двоичное дерево. В этом примере дерево содержит функцию сравнения, поэтому к типам элементов не предъявляется никаких требований.

type Tree (type E) struct {
    root    *node(E)
    compare func(E, E) int
}

type node (type E) struct {
    val         E
    left, right *node(E)
}

Так создаётся новое двоичное дерево. Функция сравнения передаётся в функцию New.

func New (type E) (cmp func(E, E) int) *Tree(E) {
    return &Tree(E){compare: cmp}
}

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

func (t *Tree(E)) find(v E) **node(E) {
    pn := &t.root
    for *pn != nil {
        switch cmp := t.compare(v, (*pn).val); {
        case cmp < 0:
            pn = &(*pn).left
        case cmp > 0:
            pn = &(*pn).right
        default:
            return pn
        }
    }
    return pn
}

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

Этот код тестирует, содержит ли дерево значение.

func (t *Tree(E)) Contains(v E) bool {
    return *t.find(e) != nil
}
This is the code for inserting a new value.
func (t *Tree(E)) Insert(v E) bool {
    pn := t.find(v)
    if *pn != nil {
        return false
    }
    *pn = &node(E){val: v}
    return true
}

Обратите внимание на аргумент типа E в типе узла. Так выглядит написание дженерик-структуры данных. Пишется так же, как обычный код на Go, за исключением того, что там и сям разбросаны аргументы типов.

Пользоваться деревом просто.

var intTree = tree.New(func(a, b int) int { return a - b })

func InsertAndCheck(v int) {
    intTree.Insert(v)
    if !intTree.Contains(v) {
        log.Fatalf("%d not found after insertion", v)
    }
}

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

Следующие шаги


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

Роберт Гризмер написал подготовительный CL, который модифицирует пакет go/types. Это позволяет протестировать, может ли код, использующий дженерики и контракты, выполнять проверку типов. Работа ещё не закончена, но для одного пакета эта фича по большей части работает, и мы продолжим разработку.

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

Хочу поблагодарить всех, кто комментировал предыдущую архитектуру, и всех, кто обсуждал возможный облик дженериков в Go. Мы прочитали все комментарии и очень благодарны за ваш труд. Без него мы не пришли бы к тому, к чему пришли.

Наша цель — создать архитектуру, позволяющую писать рассмотренные мной выше разновидности дженерик-кода, не усложняя язык настолько, чтобы он стал слишком сложен в использовании или уже не ощущался бы как Go. Мы надеемся, что эта архитектура — шаг к цели, и будем продолжать улучшать её, опираясь на свой и ваш опыт в том, что работает, а что нет. Если мы достигнем этой цели, то сможем что-то предложить для будущих версий Go.
Mail.ru Group
1 016,66
Строим Интернет
Поделиться публикацией

Похожие публикации

Комментарии 249

    –33

    FYI: Термин "Дженерики" на русский переводится как "Обобщения".

      –8
      Чего минусуете-то человека, черти? Именно так и переводится, есть даже термин «обобщённое программирование».
        +13

        Потому что не нужно переводить на русский интернационалтные термины хорошо понятные любому программисту

          –8
          Серьёзно? И часто вы говорите «фанкшн» вместо «функции» или вот «киборд» вместо «клавиатуры»?

          «Дженерики» — слово, появившееся от отсутствия образования. Придумали его те, кто учился не универистетах, а в смузишных и не знают, что есть слово «обобщения», придуманное ещё до их рождения.
            +9

            А «функции» — это исконно русское слово, вестимо?

              –2
              А я где-то про исконность говорю? Вроде нет, говорю о том, что слово существует давным-давно, а новое придумывают от необразованности.
                +6

                Вы путаете литературный перевод и профессиональный сленг/терминологию. Это разные вещи.
                Возьмите различные области — юриспруденцию, короблестроение, шахтерское дело, металлургию. Огромное количество терминов называются так, как они называются, хотя имеют перевод на русский язык. Именно потому, что это термины, и люди, оперируя ими, говорят о профессиональных сущностях, а не об общеупотребительных.
                В ИТ то же самое.

                  –2
                  А вы думаете айти в двухтысячных появилось что ли? И до него были айтишники, слово «обобщение» уже было, просто в профессию пришло много народу с непрофильным образованием и вместо нормальных терминов стало тащить слова, которые они не знали как называются на русском.
              +2
              Я прям горю желанием услышать, как же вы произносите фразу «Протестировать релиз на стейджинге и отправить его в продакшн»
                –2
                Я говорю о ситуации, где слово в языке есть, а вы пытаетесь меня подтолкнуть к ситуации, когда слова в языке нет или оно неудобное.

                Для справки: «стейдижнг» = «отладочная среда» (неудобно), «продакшн» = «продуктовая среда» (неудобно, мы говорим «прод»).
                  0
                  Возможно я просто предвзято отношусь к конкретно этому слову из-за моего окружения, как я уже писал в комменте ниже. Вообще я только недавно узнал, что дженерик — это обобщение, как-то не думал даже его переводить, до этого это для меня было слово означающее именно ту самую передачу типов в класс/функцию как в C#, где я с ними и познакомился. Поэтому для меня до недавнего времени «дженерик» был таким же словом как «тест» или «продакшн», а уж чтобы слышать его в разговорах как «обобщение» — этого я вообще от родясь не слышал.
                    –1
                    Обобщение — старое понятие, представьте, если бы в разговорах с вашими друзьями вы использовали бы «майнус» вместо «минуса», просто не знали бы, что слово «минус» уже существует.

                    Вот «дженерик» для меня — примерно то же самое.
                  0
                  — Так он же на этом скачке расколется, редиска, при первом же шухере.
                  — этот нехороший человек предаст нас при первой опасности
                  0
                  А ещё можно вспомнить «инстансы» и «хенделы», и смузи не забыть в кружечку налить ))) За инстансы ваще бы ноги выдёргивал и вичками бил — бесят до безобразия ;P
                  –6

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

                    +9
                    Нет уж увольте, хватит пытаться вручную форматировать язык, как будто он ваша личная собственность. Язык формируетя сам, так, как удобно его носителям, и они не обязаны спрашивать вашего соизволения.
                    Кстати, по поводу «отродясь не было»: термин, кортеж, академический, пигмеи.
                      –3
                      Язык формируется из двух течений: переменами и сопротивления переменами. То и другое необходимо. А вы так говорите, как будто первое важно, а второе — плохо.
                        +1
                        А вы так говорите, как будто второе важно, а первое — плохо.
                          +1
                          Я вам написал про перемены и сопротивление. Я как раз из сопротивления.
                            0
                            Так а чего вы на современном русском говорите? Говорите на языке из Слова о полку Игореве, а то, вишь, вокруг холопы распоясались, язык мелодичный поломали.
                              0
                              Я говорю важны и перемены им, и сопротивление, алло. А вы говорите «с Москвы», «на моём Айфон», «одевать ботинки»?
                                0
                                А как вы говорите: в Шереметьеве или в Шереметьево?
                                  0
                                  «В Шереметьеве», разумеется.
                        +1

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


                        Если это новый русский язык, потому что большинству так удобнее, я на таком суржике обобщатся не намерен, а люди которые этим занимаются, малограмотны и не знают ни родного языка и не познали красоты и лаконичности французского и/или английского.


                        Мое оценочное суждение.

                          –1
                          Я тут аккуратно повторюсь: вы всячески порицаете тех, кто «коверкает русский язык», при этом у вас не хватает тучи запятых, и вы стабильно не можете написать «тся/ться» по вполне себе канонiчным правилам русского языка. Это нормально? :)
                            0

                            Простите, а как вы произносите "звонит" и имеет ли для вас какое-то значение постановка ударения в данном слове?


                            ЗЫ бесят порой "борцуны" за чистоту языка, когда сами грамотно писать/говорить не способны.
                            ЗЗЫ а ещё в добавок к коментарию Сайзыймена — у вас ещё и с литературной корректностью проблемы (не согласованное предложение "Моё оценочное суждение" — пример согласованного "Это — моё оценочное суждение", такое упрощение корректно только в устной разговорной речи). И ё вы не используете, нарушая правила русского языка.

                              0
                              такое упрощение корректно только в устной разговорной речи

                              Что-то я такого правила не припомню, если на то пошло то там глагола не хватает. И это как-бы ответ на комментарий.


                              И ё вы не используете

                              ё во многих случаях необязательна к использованию. Да и если честно не знаю где на раскладке она есть(QWERTY без кириллицы). Вы историю, почему она стала обязательна к написанию в 1942г, знаете?


                              За опечатки и ошибки можно упрекнуть кого угодно.


                              Я не скрываю того факта, что из-за отсутствия многолетней практики письменного русского языка, я допускаю некоторые ошибки. Это все вопрос практики. Я никогда не претендовал на роль хранителя русской орфографии. Опечатками и отсутствием запятых языка не испортить. А вот тупым перениманием импортных терминов, можно и даже очень эффективно превратить, русский язык в подобие англо-суржика.

                                0
                                Я никогда не претендовал на роль хранителя русской орфографии. Опечатками и отсутствием запятых языка не испортить. А вот тупым перениманием импортных терминов, можно и даже очень эффективно превратить, русский язык в подобие англо-суржика.

                                Ну тогда с словарь языка тоже хранить не надо. Нежизнеспособные заимствования — умрут сами. Жизнеспособные — выживут несмотря на десятки "исторически имеющихся" аналогов. Всяческие опечатки, орфографические ошибки — считай новое слово (некоторые слова меняют написание в ходе исторического процесса языка!). Только это новое слово ещё хуже заимствованного. Пунктуация: не справляетесь со сложными предложениями — пишите простые. Вот просто ведь?! Но почему-то вам хочется использовать глубину языка, но наплевать на корректное его оформление. А это compile time error же!


                                ё во многих случаях необязательна к использованию. Да и если честно не знаю где на раскладке она есть(QWERTY без кириллицы). Вы историю, почему она стала обязательна к написанию в 1942г, знаете?

                                Ё "необязательна" только в типографике (чтоб этим советским упрощателям в аду гореть… нет, чтобы головой сперва подумать, потом уже резать "ненужные" буквы). Если вы изучали русский язык в школе, если сдавали какие-нибудь экзамены по русскому языку, то начиная с 90х обязательность корректного использования ё точно вернули. Более того — вернули-то раньше, я просто не задавался вопросом, когда именно. Для кверти-клавиатуры в ЙЦУКЕН-раскладке ё располагается вот здесь: `~.


                                Что-то я такого правила не припомню, если на то пошло то там глагола не хватает. И это как-бы ответ на комментарий.

                                Глагол в целой куче случаев можно опускать — это корректно с точки зрения литературного языка, а вот несогласованное предложение, стоящее отдельным абзацем, у вас было. Пример, как его согласовать, я вам написал.

                                +1
                                Простите, но у вас тоже «звонит» неправильно написано. Правильно — «звонит».
                            +2
                            Да, вы правы, я сто лет не читал книги по IT-тематике на русском языке, все больше как-то на английском. Зато в диалогах с зарубежными коллегами не возникает никаких проблем в распознавании тех или иных терминов. Сколько себя помню, ни от одного коллеги, ни от русского, ни тем более от иностранца, никогда не слышал слова «обобщения» в значении «дженерик». Возможно в Вашем круге общения все кругом употребляют «обобщения» вместо «дженериков», но у меня это совершенно не так. Поэтому я ума не приложу зачем переводить на русский термин, который давным давно используется повсеместно.
                              0
                              То есть вы так и говорите с зарубежными коллегами: «Протестировать релиз на стейджинге и отправить его в продакшн» или всё же остальные слова так же переводите на английский?
                                0
                                Разумеется перевожу, но удобно, что спокойно получается использовать одну и ту же терминологию и с иностранцами и с соотечественниками
                                  0
                                  То есть 99,99% слов вы переводите, а тут нужен другой принцип?
                                    +1
                                    Разумеется, я перевожу все, что не относится к специфичной профессиональной лексике, в отношении же специфичных слов — тут уже как больше принято или как меня лучше поймут. В конце концов вне зависимости от того, на каком языке человек разговаривает, гуглим мы вопросы по программированию в 90% случаев на английском, я так может и в 99.99% случаев, поэтому и слово «generic» гораздо большему числу людей будет понятно, чем «обобщение». У меня случались ситуации, когда человек не знал слово «дженерик», но в 100% случаев это был человек, который либо в принципе никогда не сталкивался с такой конструкцией языка, либо просто не знал, как такая конструкция в принципе называется
                                0

                                Наверное вам не повезло обучатся в наших ВШ и работать с коллегами которые там обучались.


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

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


                                Я живу на западе и работаю на британскую контору, но никаких проблем с общением с коллегами не испытываю и даже в мыслях никакие "дженерики" не появляются. Как с ВШ осталось, шаблоны проектирования, обобщения итд, так оно и есть, все остальное от малограмотности.

                                  0
                                  Для человека, который бравирует своим образованием и выступает за чистоту русского языка, вы совершаете многовато детских ошибок. :)
                                  tsya.ru, запятая после «Наверное», запятая перед придаточным в сложноподчинённом предложении.
                                    0
                                    вы совершаете многовато детских ошибок. :)
                                    запятая после «Наверное», запятая перед придаточным в сложноподчинённом предложении.

                                    Хм, пунктуация в русском языке, тем более, в вышеописанных случаях никогда не являлась делом детским. Вы без вашего сервиса навряд ли смогли бы найти такие ошибки. Я по-русски не пишу уже много лет, все как-то по фр или по английски, а у них с пунктуацией намного проще. Есть все-таки плохое влияние.

                                      0
                                      Запятые в сложноподчинённых предложениях и после вводных слов проходятся, кажется, в 6 классе школы.
                                      Я сервисом не пользуюсь, мне это и без того глаз режет.
                                        0

                                        Нет, если мне память не изменяет, то это класс так 10.

                                          0
                                          Специально полез проверять: самое начало девятого.
                                          Вот вводные слова — 8-й класс.

                                          Ну в общем это всё ещё не вяжется с ратованием за чистоту русского языка, если честно.
                          +6
                          1C программист?
                            –6
                            За что же вы так родной язык-то не любите? Дефис-то поставьте.
                              +2
                              Понравился ваш пост с названием «флоатинг», красиво
                              bolknote.ru/selected
                                0
                                Какое слово вы предлагаете использовать?
                                0
                                не угадал, насчет родного языка!
                              0
                              Я согласен в том плане, что люди, которые говорят «дженерики» в большинстве своём знакомы с ними по конкретной реализации (например, в Java) и не очень представляют что это за понятие, что оно подразумевает и как реализовано в разных языках. Потому что обобщённое программирование есть во всех динамически типизируемых языках по определению (от LISP до Python и JS), в Haskell, в Rust… и зачастую требования «сделайте нам дженерики» подразумевает «сделайте как в C#/Java/C++», что является неверной постановкой вопроса.
                              Говорить «дженерики» — имхо, то же самое, что говорить «чай матЭ» («матЭ» по-испански — «убил», а трава называется правильно «йЕрба мАтэ») или «ты какой чай пьёшь: чёрный или зелёный?» («я люблю разный чай — от гуандунских улунов до хэй ча»).
                              +11

                              Огромный вопрос — кому, собственно, Ян все это рассказывает? На планете вообще остался хоть кто-то, связанный с программированием, но не в курсе, что такое и зачем нужны дженерики?

                                +5
                                Ну те кто работают с динамической типизацией, могут быть не в курсе.
                                  +3
                                  А ещё те, кто последние 5 лет кричали: «в Go не нужны дженерики, там уже есть типизированные массивы!!»
                                    –3

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


                                    Кубернетис с докером прекрасно написали без дженериков, да.

                                      0

                                      Может вам и была, а многим — не была.

                                        –1

                                        Я скорее не про себя, а про команду го и её мнение на этот счёт, да и про сообщество.
                                        Дженерики нужны, но не настолько, чтобы бежать их пилить сломя голову – позиция, которая была очень давно и которой придерживалось большинство. "Мы не знаем, как их сейчас сделать правильно" и вот это вот всё.


                                        Я как раз 5 лет на го и пишу, так что с моей точки зрения всё происходило примерно так, и данный пропозал – логичное продолжение работы над языком.


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

                                          +3
                                          «Мы не знаем, как их сейчас сделать правильно» и вот это вот всё.

                                          Почему-то такая логика не помешала сделать ужас с $GOPATH, а потом его починить модулями.
                                        +11

                                        О да, тот самый Kubernetes, которые фактически написали свои собственные generics, чтобы не страдать. А так конечно необходимость преувеличивают.

                                          –1

                                          Отсутствие generics не помешало Kubernetes развиться до текущего состояния.


                                          А так конечно необходимость преувеличивают.

                                          Не все пишут проекты уровня Kubernetes, да и проблема обычно решается другим подходом к проектированию, без попыток писать на Go как на C#, Rust или Python.
                                          Я не погружался в то, что сделали в кодовой базе Kubernetes, так что не могу ничего сказать по этому поводу, но предполагаю, что проблему можно было решить иначе. Судя по последним изменениям в k8s.io/apimachinery, наблюдается движения к более строгой типизации.


                                          Пятилетний опыт коммерческой разработки на Go показал, что дженерики не нужны для большинства решаемых задач (но это не значит, что с ними в некоторых местах было бы удобнее и проще). Но вам, конечно, виднее.

                                          +4
                                          Как у человека, который сейчас активно пишет на го, у меня складывается впечатление, что Роб Пайк сотоварищи находились в криогенном сне последние 25 лет.
                                          Иначе то, что они выкатили на-гора в конце нулевых статически типизированный язык без дженериков и с обработкой ошибок, сделанной хуже, чем в, прости господи, Си, объяснить мне кажется сложным.

                                          И да, без них больно и неприятно. Без человеческой обработки ошибок ещё хуже, конечно.
                                            0

                                            Вот как раз с обработкой ошибок, очень субъективно, дела идут прекрасно с точки зрения языковых конструкций. Ровно до того момента, когда надо проверять, не игнорируется ли ошибка (а именно в этом корень всех претензий). Я тогда предлагал в компилятор просто повесить проверку, не игнорируется ли из любой функции возвращаемый параметр типа Error. Да, привет утиной типизации, с которой это труднореализуемо. Да, вопрос тонны уже написанного кода при этом открыт.

                                              +4
                                              Нет-нет-нет, проблема даже не в этом.
                                              Умолчания языка с точки зрения обработки ошибок абсолютно ужасны, потому что у вас ошибки по умолчанию (в стандартной библиотеке в том числе) из каких-то проверяемых хотя бы теоретически значений возвращают только строчку с текстом. А строчка с текстом зависит не то что от ОС, а от локализации, и от кучи чего ещё. У вас нет нормального способа обработать ошибку, потому что вы знаете только "… ну что-то пошло не так". Чтобы содержательно реагировать на ошибку, эту ошибку надо уметь отличать от какой-то другой ошибки.
                                              Файл не найден — одно. К файлу нет доступа — другое. Что-то третье — ну, оно третье.

                                              Они теперь, конечно, пытаются это как-то исправить, но да, есть тонна написанного уже кода, который никто не перепишет, и сконкатенированные четыре сообщения об ошибке с тремя двоеточиями (это когда кто-то четырежды написал if err != nil, из них три раза это обёртки своего кода, и только четвёртый по вложенности вызов содержит, собственно, проблему и её описание, а обработать это нельзя, потому тексты просто склеиваются и логируются) будут сниться вам ещё 10 лет.
                                                0

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

                                                  +2
                                                  Ну да, только проблему всего существующего кода это уже не решает, действительно, и, кажется, это грустно.

                                                  Ключи го не любит, мне кажется.
                                                  Вон, даже неиспользуемые импорты и переменные им ни-ни. А в ходе разработки это болезненно — хочу я протестить небольшой кусок кода, закомментировал использование переменной или вызов внешнего пакета и сгорел лезу убирать импорт, заменять присваивание переменной на _, и тем самым трачу время на удовлетворение синдрома вахтёра компилятора.
                                                    –1
                                                    да ладно вам, убирать неиспользованные импорты не надо. Вроде есть Тулы (не знаю если gofmt это делает), который убирает или пересобирает импорты, так что проблема будет только в переменной
                                              +3
                                              > складывается впечатление, что Роб Пайк сотоварищи находились в криогенном сне последние 25 лет.

                                              Моё субьективное после плюсов, совершенно обратное. Затащить фич, вообще не проблема — просто потом боротся с ними тяжко. Более 16 ГБ для линковки, 40 минут билда на многоядерных ксеонах и SSD-ях в CI, разные ccache у разработчиков, эпизодические работы по ускорению билдов. Вообщем я рад что некоторые вещи можно перетащить на go.
                                                +4
                                                Ну плюсы — пример ещё более плохого языкового дизайна, но у них хотя бы есть оправдание (легаси, обратная совместимость...), Пайк же сразу начал с плохого.

                                                Ну и да, языковые фичи сами по себе совершенно необязательно транслируются в увеличение времени билда и затрат памяти.
                                                  –1

                                                  Плюсы собираются по 40 минут и жрут память как не в себя не из-за фич, а из-за "особенностей процесса". Java на тот же объём фич и кода затрачивает на пару порядков меньше времени и на порядок-два меньше памяти. Потому-что при создании языка этот момент хоть чуть-чуть, но продумали. И упростили. И да, те же дженерики — почти бесплатные с точки зрения компиляции и совсем бесплатные в рантайме (впрочем в рантайме их по сути-то и нет).

                                                    +1
                                                    на мой взгляд вы не знаете о чем говорите. Тэмплэйты в плюсах это вообще не дженерики. Да о чем говорить если даже тэмплэйты Turing complete, только variadic templates чего стоят. У джава тот же объем фич. Ну ну
                                                      +1
                                                      В TypeScript система типов тоже тьюринг-полная.
                                                          0

                                                          А теперь докажи, что я уравнял дженерики и темплейты. Просто темплейты в джава не нужны — хватает куда более простой абстракции.

                                                            –1

                                                            Ага, не нужны. То-то все плачут про type erasure.

                                                              +1
                                                              Ну при чем тут темплейты? В C# те же Дженерики, но очистки типов там нету
                                                                0

                                                                В C# те же шаблоны, только специализируются они в рантайме.

                                                                  0
                                                                  Они все же ближе к Джаве, чем к С++.
                                                                    –1

                                                                    Да какая разница к чему он ближе, если по факту это шаблоны.

                                                                      0
                                                                      Ну тогда и в джаве шаблоны
                                                                        –1

                                                                        Там контейнеры.

                                                                    0

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


                                                                    https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/differences-between-cpp-templates-and-csharp-generics

                                                                      0

                                                                      А я где-то утверждал обратное?

                                                                        +1
                                                                        В C# те же шаблоны, только специализируются они в рантайме.

                                                                        или


                                                                        Да какая разница к чему он ближе, если по факту это шаблоны.

                                                                        Проблемы с памятью?

                                                                          0

                                                                          А давайте вы прежде чем хамить, пройдёте по ссылке и почитаете, что такое генерики?
                                                                          https://habr.com/ru/company/mailru/blog/462811/#comment_20491449

                                                                            +1

                                                                            Я вам не хамлю. Я констатировал факт того, что вы забыли свои же недавние фразы.


                                                                            Следуя оффициальной терминологии в C# именно что generics. Причем "Generic programming" != "Generics".


                                                                            Прежде чем пороть чушь, пройдите по своей ссылке, и почитайте что в C# именно что generics, и что даже code generation является одним из подходов к Generic programming. И что type erasure не является необходимым атрибутом generic'ов. И вот дабы это наглядно показать, вам привели в пример C#. На что в ответ пошла чушь навроде "В C# те же шаблоны". Глупо и некрасиво.

                                                                              0

                                                                              Обобщённое программирование (generic programming) — это программирование с использование обобщений (generics). Есть два варианта реализации обобщений:


                                                                              1. кодогенерация на основе шаблонов (templates).
                                                                              2. заворачивание разных типов в единый контейнер (boxing).

                                                                              Сравнивая дженерики с шаблонами вы проявляете свою безграмотность в этом вопросе. Не надо так. На этом у меня всё. Все хорошего.

                                                                                0
                                                                                В C# не используется кодогенерация
                                                                                0

                                                                                Ага-ага. А процедурное программирование — это программирование с использованием процедур. А структурное программирование — это программирование с использованием структур. А функциональное — это когда программирование с использованием функций.


                                                                                Короче опять чушь несете.

                                                                  +1

                                                                  type erasure запили как компромисс, который позволили по минимум переделывать виртуалку, коих для джавы мнооого. А еще сделать весь старый код совместимым с новой джавой.


                                                                  Наличие type erasure в обной из реализаций дженериков не означает что оно есть везде.

                                                                  0
                                                                  Ну как я понял, вы имели в виду, что в Java такие же фичи, что в плюсах.
                                                                  С одной стороны, эти языки Turing Complete, с другой стороны, вы не можете без рефлексии/кастов написать variadic templates.
                                                                  хватает куда более простой абстракции.

                                                                  кому-то хватает, кому-то нет
                                                          +4
                                                          Только вот маленькая ремарка. Им пришлось накостылять свои собственные «дженерики» для решения их задач. А так да. Написано без дженериков.
                                                            0

                                                            Ага, спокойно...

                                                      +1
                                                      Все выглядит вполне приятно. Интересно, есть ли какие-то минусы — лично я сходу не нашел.
                                                        0
                                                        Минусы всплываю не быстро.
                                                        Вон сколько времени try обсуждали и таки позже насыпали таких минусов, что тему закрыли.

                                                        Тут ведь важно как это еще тот же дебаггер будет отрабатывать и прочие мелочи. ИМХО именно дебаггинг и code coverage в тестах похоронили try.
                                                          +3

                                                          Претензии к try всплыли моментально — странные неявные манипуляции с переменной ошибки только ленивый не заметил.

                                                            0
                                                            Основные претензии — да всплыли моментально, но именно накопление всего разнообразия минусов сыграло роль в финале (на мой взгляд).
                                                            0
                                                            А мне кажется выстрелит. Т.е. по сути может у них получится что то типа с++ темплейтов, где для каждой отдельной комбинации дженерик аргументов будет за кулисами сгенерировано отдельную функцию(т.е ReverseAndPrint(int)() и ReverseAndPrint(map[string]string) будут двумя разными методами). С дебаггингом тут будет попроще, чем с try.
                                                            0
                                                            Ну уже появляются статьи с критикой.
                                                            Вот эта например — My Case Against the Contracts Proposal for Go.
                                                            Я с ней не согласен, но почитать интересно.
                                                              +1

                                                              Да там такое впечатление, что человек читал спеку наспех по диагонали.

                                                                +1
                                                                Ну да. Статья так себе, но надеюсь, что за ней последуют и другие.
                                                                Важно чтобы пропозал обсуждали и критикивали, иначе есть опасность не заметить серьезных недостатков.
                                                                Я считаю, что лучше никаких дженериков чем плохие.
                                                                Хотя данный пропозал на первый взгляд выглядид неплохо.
                                                                  0

                                                                  Пока что у меня придирка к существующему апи map[int]int — оно теперь выглядит белой вороной.

                                                                    0

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

                                                              +3

                                                              Вас не смущает, что Ordered является просто перечислением возможных типов и это считается нормальным?

                                                              0
                                                              камент для удаления
                                                                +5

                                                                Такое ощущение, что предлагается использовать скобки () вместо <>, как в других языках, по каким-то религиозным соображениям.

                                                                  +6
                                                                  «Треугольные» кавычки (<>), в отличии от настоящих кавычек ([]{}) являются еще и бинарными операторами сравнения, из-за чего не обязаны быть парными в валидном коде. Это мешает нормальному здорову сну разработчиков парсеров.

                                                                  Вот вам пример из раста, который хотел выглядеть как C++:
                                                                  if a as u32 < 5 { ... }

                                                                  Ответ заботливого компилятора
                                                                  error: `<` is interpreted as a start of generic arguments for `u32`, not a comparison
                                                                   --> src/main.rs:2:17
                                                                    |
                                                                  2 |     if a as u32 < 5 {}
                                                                    |        -------- ^ --- interpreted as generic arguments
                                                                    |        |        |
                                                                    |        |        not interpreted as comparison
                                                                    |        help: try comparing the cast value: `(a as u32)`
                                                                  


                                                                  Дальше больше:
                                                                  iter.collect::<Map>()

                                                                  Знаете зачем здесь четвероточие? Чтобы компилятор не сошел с ума в попытках понять зачем вам нужно условие о том что утверждение о том что iter.collect меньше Map является больше пустого кортежа:

                                                                  (iter.collect < Map) > ()
                                                                    –3
                                                                    Выглядит, как гниль в спецификации языка.
                                                                    Какой-то C# даже близко не имеет таких проблем.
                                                                      +12
                                                                      Извините, кажется у вас C# прогнил:

                                                                      image

                                                                      Будете учить новый язык? Не на гнилом же писать…
                                                                        –2

                                                                        Эмс… а что этот код вообще значит? Кажется, я неправильно вас понял. Вы хотите писать ничего не значащий код и ждете, чтобы он заработал. Простите, не понимаю ваш пример

                                                                          +6
                                                                          Что не понятного? Так работает:

                                                                          bool b = (f as F) < a;

                                                                          А так не работает, потому что парсер решил что я пытаюсь задать параметризацию типа:

                                                                          bool b = f as F < a;

                                                                          А так снова заработало:

                                                                          bool b = f as F > a;
                                                                            +1

                                                                            Все, теперь понял. Я просто тоже так решил.

                                                                              +12
                                                                              Ну вот. Знаки сравнения в языке обрабатываются по разному. Дыра в спецификации!
                                                                        0
                                                                        Это из спека C# 4-й версии, но более поздние имеют то же самое по сути:

                                                                        If a sequence of tokens can be parsed (in context) as a simple-name (§7.6.2), member-access (§7.6.4), or pointer-member-access (§18.5.2) ending with a type-argument-list (§4.4.1), the token immediately following the closing > token is examined. If it is one of
                                                                        ( ) ] }:;,.? == != | ^
                                                                        then the type-argument-list is retained as part of the simple-name, member-access or pointer-member-access and any other possible parse of the sequence of tokens is discarded. Otherwise, the type-argument-list is not considered to be part of the simple-name, member-access or pointer-member-access, even if there is no other possible parse of the sequence of tokens. Note that these rules are not applied when parsing a type-argument-list in a namespace-or-type-name (§3.8). The statement
                                                                        F(G<A,B>(7));
                                                                        will, according to this rule, be interpreted as a call to F with one argument, which is a call to a generic method G with two type arguments and one regular argument. The statements
                                                                        F(G < A, B > 7);
                                                                        F(G < A, B >> 7);
                                                                        will each be interpreted as a call to F with two arguments. The statement
                                                                        x = F < A > +y;
                                                                        will be interpreted as a less than operator, greater than operator, and unary plus operator, as if the statement had been written x = (F < A) > (+y), instead of as a simple-name with a type-argument-list followed by a binary plus operator. In the statement
                                                                        x = y is C<T> + z;
                                                                        the tokens C<T> are interpreted as a namespace-or-type-name with a type-argument-list.


                                                                        Эта диверсия с <> — она везде имеет похожие последствия в неоднозначности, так что непонятно, зачем было её вообще размножать.
                                                                        +1

                                                                        Ещё не забудьте бинарный сдвиг >>, который должен закрывать вложенный дженерик. В старых плюсах обязательно было пробел ставить в середине.

                                                                        0

                                                                        Простота парсера и сохранение привычного для го синтаксиса это не религиозные соображения.
                                                                        Для человека, который достаточно долго пишет на го, параметризация через скобки выглядит привычнее.
                                                                        Просто так делать "как в других языках" не получится.

                                                                            +1
                                                                            Да какие проблемы? В аннотациях типов Python используются []
                                                                            Пусть уже сделают наконец, а то 10 лет языку, а на нём писать нормально нельзя.
                                                                            Они настолько заигрались с тем, чтобы сделать трудным написание плохих программ, что сделали трудным написание хороших.
                                                                            0
                                                                            А вот что может удивить новичков в Go, так это отсутствие способа написать простую функцию Reverse, работающую со слайсами любых типов.

                                                                            Можно ведь написать функцию которая работает со слайсом любого типа, что вы обманываете новичков?
                                                                              +2

                                                                              Как? Через interface{} с последующим использованием Reflect? Этот способ в статье описан. Если вы про другой, то можно пример?

                                                                                0

                                                                                Нельзя. []interface{} это отдельный тип и к, допустим, []int он никак не кастится.

                                                                                  –3
                                                                                  так в примере то было пофигу какой тип, там просто происходила работа со слайсом «чего-то»
                                                                                    +1
                                                                                    И что? Как юзать такую функцию?
                                                                                    Если сделать func(s []interface{}) то передать в нее []int нельзя.
                                                                                    Если сделать func(s interface{}) то внутри нужно делать reflect или type switch для каждого поддерживаемого типа.
                                                                                      0

                                                                                      Можно использовать функцию CastIntArrayToInterfaceArray, а потом, на том результате, который вернулся — использовать функцию CastInterfaceArrayToIntArray. Я, кстати, видел такое в реальном коде на Гоу

                                                                                        0
                                                                                        Ужос какой. Уже кровь из глаз пошла :)

                                                                                        Слава богу у меня в реальных проектах нужда в дженериках не настолько сильно возникала… Да, я писал пару ReverseXXX, но не более того.
                                                                                        0
                                                                                        Да, я затупил, постоянно забываю про слайс интерфейсов ибо не использую такое, согласен, нельзя.
                                                                                  +2

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

                                                                                    +1
                                                                                    В отличие от дженериков, которые будут определяться в compile-time, интерфейсы будут определяться в run-time. А из этого следует:
                                                                                    — интерфейсы приходят не бесплатно
                                                                                    — интерфейсы могут быть источником неявных ошибок
                                                                                    И как бонус: в IDE в функцию, в сигнатуре которой есть интерфейс(ы), можно передать что угодно, ошибка не высветится.
                                                                                      –1
                                                                                      вообще-то высветится, подобие интерфейса проверяется на этапе компиляции
                                                                                      пруф: play.golang.org/p/USNSdEWJTm5
                                                                                      Но тем не менее контракты довольно крутая штука, было бы прикольно как типы их использовать вне концепции дженериков
                                                                                        0
                                                                                        Ну и какое отношение имеет это к дженерикам? Речь-то об этом:

                                                                                        func dummy(q interface{}) {
                                                                                            // здесь детект типа, и если тип не поддерживается, то вызываем panic()
                                                                                        }
                                                                                        


                                                                                        Ясно, что ни о каком compile-time речи не идёт. Тип будет определяться в рантайме. И подсветить неподдерживаемый тип IDE не сможет (если только кто-то, не дай бог, не додумается вводить в go аннотации в комментариях, а-ля PHP).

                                                                                        При этом опять же очевидно, что если код слабо покрыт тестами, то не исключено, что где-то в функцию может быть передано значение с неподдерживаемым типом. И всё, привет panic.
                                                                                          –1
                                                                                          зачем же пустой интерфейс использовать? их же не для того добавили. В го просто контракты как типы и без дженериков уже была бы крутой фичей. Дженерики по большей степени не нужны.
                                                                                            +1
                                                                                            Дженерики по большей степени не нужны
                                                                                            Ну и как в Go написать, к примеру, map/filter/reduce?
                                                                                              0

                                                                                              Через анонимную функцию, внезапно

                                                                                                0
                                                                                                Правильно, давайте заставлять программистов писать в 2 раза больше бойлерплейта и периодически класть болт на type safety в статически типизированном языке. :)
                                                                                                  +1
                                                                                                  Чтож там у вас за бойлерплейт такой? или у вас содержимое мап функций всегда повторяется? Чета я не верю, в большинстве случаев даже сигнатура не схожа. Да и в принципе, написать лишних 20 символов это очень большая нагрузка на бедных программистов. Вы уже написали только что кучу бойлерплейта, ну что, сильно устали? Вы наверно так устали что следующий камент будет через час минимум. А вы знали что мап/редьюс можно вообще без функций писать?
                                                                                                  Если вам так нужны дженерики, почему не выбираете язык где они есть c#/f#/rust/typescript? Зачем вы мучаетесь с го делая тонны лишнего бойлерплейта?
                                                                                                    0
                                                                                                    Там, где выбор за мной, я действительно выбираю не го и рекомендую не го. Но, внезапно, компания, где я рядовой сотрудник, под меня не подстраивается (и устраивался я туда не го-девелопером, да, так вышло).
                                                                                                      +1
                                                                                                      Вы уже написали только что кучу бойлерплейта, ну что, сильно устали?

                                                                                                      Дело же не в усталости, дело в вручную написанном (в худшем случае скопипащенном неизвестно откуда) коде. Больше ручного кода — больше шансов на механическую ошибку. При этом нельзя перегибать палку, как в скриптовых языках, толерантных к приведениям типов — теряется контроль за данными в рантайме.

                                                                                                        –1
                                                                                                        ну я как бы стараюсь не использовать interface{} в своем коде, если использовать интерфейсы так по назначению, сделать нормальный метод с которым будем работать, сделать функцию которая его принимает, и с ним работает, и дальше добавляйте метод любому типу с которым работаете чтоб функция могла с ним работать. Неужели это так сложно? Не будет никаких ошибок, код будет гибкий и универсальный. Вы не будете привязаны ни к какому типу, и на этапе компиляции будут проверяться все ошибки.
                                                                                                        У меня нет функций (может я не правильно пишу) которые должны работать с разными типами, неужели сигнатура и содержимое двух фильтрующих функций для разных обьектов будут совпадать?
                                                                                                        Да, можно вспомнить старую забавную гифку где пишут функцию для работы с интом которая превращается в 8 из-за разных типов, но это частный случай, в большинстве случаев нет такого кода, практически всегда вы знаете точно с каким кодом работаете и можете их явно указать. Я за все время работы с го (а это не один месяц) только один раз столкнулся с функцией которая должна была работать аж с 2 типами, ибо функционал был похож.
                                                                                                        +1
                                                                                                        Если вам так нужны дженерики, почему не выбираете язык где они есть c#/f#/rust/typescript? Зачем вы мучаетесь с го делая тонны лишнего бойлерплейта?

                                                                                                        потому что это требует рынок, не потому что мы этого хотим (на F# или Rust нету проектов, работы как на Go)
                                                                                                          –1
                                                                                                          Из 4-х вариантов вы выбрали 2 с наименьшим количеством вакансий и все? У оставшихся вариантов вакансий значительно больше чем на го, по сравнению с го вас просто завалят работой, если что.
                                                                                                            +1
                                                                                                            занимаюсь SRE и без Go никуда. Все проекты на CNCF на Go ;(
                                                                                                      0
                                                                                                      Через анонимную функцию, внезапно
                                                                                                      Да? Расскажите мне, пожалуйста, как это сделать. Только так, чтобы:
                                                                                                      1. Я мог написать функцию под любой массив. Чтобы не нужно было под каждый тип копипастить код
                                                                                                      2. В filter и map возвращался массив того же типа, что я передал
                                                                                                      3. Чтобы анонимные функции принимали только тот аргумент, который я передал. Чтобы нельзя было передать функцию, которая принимает строку в качестве аргумента вместе с массивом интом
                                                                                                        0
                                                                                                        Ухты, а напишите мне функцию с дженериком чтоб:
                                                                                                        [{a:1}, {a:2}, {a:3}]
                                                                                                        [{b:1}, {b:2}, {b:3}]
                                                                                                        [1,2,3]

                                                                                                        эти 3 массива можно было фильтровать или мапом пройтись. Вы же под любой массив хотите?
                                                                                                        И как вы собираетесь работать с универсальной мап функцией, когда работаете с разными данными?
                                                                                                        Вы ведь понимаете что анонимная функция — функция которую вы только что написали? Мап это не обязательно метод, это еще может быть hoc функция, просто код без функции.
                                                                                                        Или это очень большая проблема написать лишних 20 символов чтоб типы указать?
                                                                                                        Или это большая проблема указать интерфейс который реализует явно методы для перебора или фильтров?
                                                                                                        Вы не в курсе как работать с интерфейсами в го? Не всегда нужен тип, а иногда лучше когда его нет.
                                                                                                        Приведите мне пример массивов с которыми вам нужно работать, а не просто набор миллиона типов для того чтоб меня озадачить, и я дам вам реализацию.
                                                                                                          +1
                                                                                                          И как вы собираетесь работать с универсальной мап функцией, когда работаете с разными данными?
                                                                                                          Элементарно на любом языке с дженериками.

                                                                                                          Вы ведь понимаете что анонимная функция — функция которую вы только что написали? Мап это не обязательно метод, это еще может быть hoc функция, просто код без функции.
                                                                                                          Да, понимаю.

                                                                                                          Или это очень большая проблема написать лишних 20 символов чтоб типы указать?
                                                                                                          1. Да, проблема
                                                                                                          2. В Гоу вам 20 символов не поможет, надо под каждый тип создавать свою функцию

                                                                                                          Или это большая проблема указать интерфейс который реализует явно методы для перебора или фильтров?
                                                                                                          Это не поможет написать универсальные фильтр и мап.

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

                                                                                                          Приведите мне пример массивов с которыми вам нужно работать, а не просто набор миллиона типов для того чтоб меня озадачить, и я дам вам реализацию.
                                                                                                          Ваши два массива меня устраивают. А лучше давайте 5 разных массивов.
                                                                                                          [{a:1}, {a:2}, {a:3}]
                                                                                                          [{b:1}, {b:2}, {b:3}]
                                                                                                          [{c:1}, {c:2}, {c:3}]
                                                                                                          [{d:1}, {d:2}, {d:3}]
                                                                                                          [{e:1}, {e:2}, {e:3}]
                                                                                                          [{f:1}, {f:2}, {f:3}]


                                                                                                          Ухты, а напишите мне функцию с дженериком чтоб:
                                                                                                          [{a:1}, {a:2}, {a:3}]
                                                                                                          [{b:1}, {b:2}, {b:3}]
                                                                                                          [1,2,3]

                                                                                                          Запросто, на любом адекватном языке. Так будет на TS:
                                                                                                          type A = { a: number };
                                                                                                          type B = { b: number };
                                                                                                          
                                                                                                          var aArr = [{a:1}, {a:2}, {a:3}] as A[];
                                                                                                          var bArr = [{b:1}, {b:2}, {b:3}] as B[];
                                                                                                          
                                                                                                          function myMap<TIn, TOut> (arr: TIn[], cb: (item: TIn) => TOut): TOut[] {
                                                                                                              var result: TOut[] = [];
                                                                                                          
                                                                                                              for (var it of arr) {
                                                                                                                  result.push( cb(it) );
                                                                                                              }
                                                                                                          
                                                                                                              return result;
                                                                                                          }
                                                                                                          function myFilter<T> (arr: T[], cb: (item: T) => boolean): T[] {
                                                                                                              var result: T[] = [];
                                                                                                          
                                                                                                              for (var it of arr) {
                                                                                                                  if (cb(it)) {
                                                                                                                      result.push( it );
                                                                                                                  }
                                                                                                              }
                                                                                                          
                                                                                                              return result;
                                                                                                          }
                                                                                                          
                                                                                                          console.log( myMap(aArr, it => it.a) ); // [ 1, 2, 3 ]
                                                                                                          console.log( myMap(bArr, it => it.b) ); // [ 1, 2, 3 ]
                                                                                                          
                                                                                                          console.log( myFilter(aArr, it => it.a == 2) ); // [ { a: 2 } ]
                                                                                                          console.log( myFilter(bArr, it => it.b == 2) ); // [ { b: 2 } ]


                                                                                                          Могу такой же написать, к примеру, на C#
                                                                                                            0
                                                                                                            Ну и возвращается не только корректные данные, но и тип правильный, который далее можно использовать:

                                                                                                              –1
                                                                                                              И вот теперь главный вопрос: а почему ж вы пытаетесь все это затащить в го? Вы ж пишете на ТС или С#?
                                                                                                              в го вот этот кусок it => it.a == 2 уйдет в реализацию интерфейса для типа с которым работаешь. Интерфейс с одним методом который фильтрует по полю.
                                                                                                              Я ща прям предвижу ответ «но мне же надо 100500 разных типов так фильтровать» или «но мне же надо 100500 разных фильтров». И сразу отвечу, нет, это вы ща синтетику пытаетесь пропихнуть чтоб показать как в го это сильно сложней делается. И я не говорю что в го это супер элементарно и просто. Другое дело что в реальном коде я такого почему-то не встречал, странно правда? Или у вас реально приходят 100500 разных данных в фильтр и вы используете 100500 разных фильтрах на этих данных? Я даже на тс до такого не дохожу.
                                                                                                              Не надо делать из го шарпы, пишите на шарпе. в большинстве случаев вы такой код пишете только на хабре чтоб что-то кому-то доказать.

                                                                                                              И в итоге нет, 20 символов не проблема, и вам не нужно «для всех» типов писать эти 20 символов, не надо глобализацию устраивать, вы прекрасно знаете что «для всех» ничего не нужно делать.
                                                                                                                0
                                                                                                                Так покажите пример.
                                                                                                                  –2
                                                                                                                  Это вы ждете что я напишу для каждого типа реализацию и вы скажете «ага, я же говорил, а теперь еще тип добавьте и будет копипаст», да, так и будет. и?
                                                                                                                    0
                                                                                                                    Значит, универсальные map и filter написать нельзя?
                                                                                                                      0
                                                                                                                      «it.a == 2» — вот эта строка будет копироваться для каждого типа с которым я работаю. Можно ли считать функцию которая принимает что-то попадающее под сигнатуру и работает с ним вне зависимости от типа универсальной?
                                                                                                                        +1
                                                                                                                        Я вас не понимаю, вы можете написать пример кода? Вы хотите в качестве фильтра пользовать строку?
                                                                                                                          0
                                                                                                                          Нет, не строку, я хотел отдать туда массив интерфейсов.
                                                                                                                          Но дело взяло неожиданный поворот.
                                                                                                                          Беру свои слова, ибо я в очередной раз вспомнил про одну особенность в го, которую я успешно про*бал когда с вами общался.
                                                                                                                          В идеале все должно было выглядеть так: play.golang.org/p/yG5SP2NaHXR но дело в том что го работает с массивами интерфейсов через одно место, и в итоге то что я говорил не будет работать. :)
                                                                                                                            +2
                                                                                                                            Ну от идеала очень далеко. Вам необходимо в таком случае дублировать код не только для разных типов, но и для разных колбеков, по которым вы делаете фильтр. По сути вы вообще не написали функцию filter, а каждый раз придётся писать её полностью с нуля.
                                                                                                                              0
                                                                                                                              Сколько у вас в текущем проекте функций filter? Я согласен с дублированием и уже взял свои слова назад. Мне просто интересно на сколько часто вам нужна такая функция. Я просто ща подумал что за последние 4 года мне такая функция понадобилась от силы раз 5, это я беру в учет 4 последние проекта. Мап/редьюс и того меньше. И все эти 4 раза работали с парочкой типов данных от силы.
                                                                                                                              Может я со своей колокольни по незнанию так сопротивляюсь, а в других местах они часто используются.
                                                                                                                                0
                                                                                                                                У меня в проекте дженерики используются очень часто. Много вещей в гоу стали бы лучше, если бы были дженерики. Да, везде можно закопипастить +3 строки, +2 строки. Этой всей копипастой в итоге код на Гоу и выглядит является таким говнокодистым.

                                                                                                                                Ну вот, к примеру, JSON:
                                                                                                                                var bird Bird	
                                                                                                                                json.Unmarshal( []byte(birdJson), &bird )

                                                                                                                                vs
                                                                                                                                bird := json.Unmarshal<Bird>( []byte(birdJson) )


                                                                                                                                Ну или, допустим, пойдем на уровень выше с этим JSON. Мы хотим, чтобы наш Rest API фреймворк сам занимался обработкой ошибок некорретного JSON. С Дженериками можно было бы написать что-то такое:

                                                                                                                                fw.Get<IdJson>("/book", func (json IdJson) {
                                                                                                                                    return Render(GetBook(json.id));
                                                                                                                                })
                                                                                                                                
                                                                                                                                fw.Post<BookJson>("/book", func (json BookJson) {
                                                                                                                                    SaveBook(json)
                                                                                                                                    return Ok();
                                                                                                                                })


                                                                                                                                Без дженериков такой-же код выглядит как-то так:
                                                                                                                                fw.Get("/book", func (c fw.Context) {
                                                                                                                                    var idJson IdJson	
                                                                                                                                    error := json.Unmarshal( []byte(c.body), &idJson )
                                                                                                                                	
                                                                                                                                    if error {
                                                                                                                                        return RenderError(error);
                                                                                                                                    }
                                                                                                                                
                                                                                                                                    return Render(GetBook(idJson.id));
                                                                                                                                })
                                                                                                                                
                                                                                                                                fw.Post("/book", func (c fw.Context) {
                                                                                                                                    var bookJson BookJson	
                                                                                                                                    error := json.Unmarshal( []byte(c.body), &bookJson )
                                                                                                                                	
                                                                                                                                    if error {
                                                                                                                                        return RenderError(error);
                                                                                                                                    }
                                                                                                                                	
                                                                                                                                    SaveBook(bookJson)
                                                                                                                                    return Ok();
                                                                                                                                })


                                                                                                                                Тот же код, но теперь тьма ненужной копипасты в каждом эндпоинте и куча мест для ошибки.
                                                                                                                                  0
                                                                                                                                  в первом примере вы забыли что анмаршал ошибку возвращает и это важный момент, пример мне не нравится, разница в пару символов не влияет на «это же короче писать», а читабельность не улучшается, ибо текущий вариант очень понятный сходу.
                                                                                                                                  А второй вариант уже успешно работает, такое можно сделать (да, рефлексия или кодогенерация увы, знаю что у всех аллергия на эти слова).
                                                                                                                                  Второй вариант лучше, но вот проблема, вам ведь контекст нужен будет все равно? А если данные не жсон? Опять же пример ради примера, просто потому что вы работаете конкретно с жсон и лично вам было бы удобней так.
                                                                                                                                  Да, в го хватает копипасты, кое где дженерики ее уберут, но этих мест будет очень мало. Меня больше пугает что их начнут использовать везде, там где надо, там где не надо или просто потому что могут, и это модно стильно молодежно. Новая фича и ее обязательно нужно сунуть везде где ни попадя. И в итоге боли будет больше чем пользы.
                                                                                                                                    +1
                                                                                                                                    Меня больше пугает что ихего начнут использовать везде, там где надо, там где не надо или просто потому что могут, и это модно стильно молодежно.

                                                                                                                                    Это вы щас историю успеха Go описали?


                                                                                                                                    Не переживайте, наговнокодить можно и сейчас, было бы желание. Врядли ситация кардинально поменяется.

                                                                                                                                      –2
                                                                                                                                      Какой остроумный ответ, у вас такое хорошее чувство юмора, я оценил. Подскажите где не стоит использовать го, чтоб я случайно не полез куда не нужно?
                                                                                                                                      +1
                                                                                                                                      вам ведь контекст нужен будет все равно
                                                                                                                                      С Дженериками то? Совсем не проблема!

                                                                                                                                      fw.Get("/book", func (context JsonContext<IdJson>) {
                                                                                                                                          return Render(GetBook(context.json.id));
                                                                                                                                      })


                                                                                                                                      А если данные не жсон?
                                                                                                                                      Это же фреймворк. Ну будет тогда у нас XmlContext. Как авторы фреймворка решат — так и будет. А сейчас у них вообще возможности такой нету.

                                                                                                                                      да, рефлексия
                                                                                                                                      Да? И как при помощи рефлексии это сделать, покажите мне. Быдлокодогенерацией — да, можно, конечно.

                                                                                                                                      просто потому что вы работаете конкретно с жсон и лично вам было бы удобней так.
                                                                                                                                      Ну конкретно я работаю с JSON. А кто-то другой работает конкретно с XML. Третий работает с пейджингом, который может разбивать на страницы, а дженериком указывает, какой именно тип является ячейкой этой страницы. Как результат — код на Гоу вызывает аллергию у всех людей, у которых есть аллергия на быдлокод.

                                                                                                                                      И в итоге боли будет больше чем пользы.
                                                                                                                                      А разве это не идеология Гоу? «Боли больше чем пользы!»
                                                                                                                                0

                                                                                                                                Простите, но ваше "в идеале" вяглядит очень "не очень".
                                                                                                                                Про то ведь и речь, что по нормальному с джерериками все могло бы выглядеть наподобие https://play.golang.org/p/jJLl_CIs_1R
                                                                                                                                Никаких левых структур, никаких sInt, нужная логика в одном месте (в анонимной функции).


                                                                                                                                PS: особо хорош способ возвращения слайса to из метода filter ;)

                                                                                                                                  0
                                                                                                                                  А еще можно просто цикл написать, там всего 3 строки, вместо того чтоб делать функцию которая понадобится от силы 3 раза, добавить ее в какой-то пакет непонятный (привет «utils») про который успешно забудет кто-то в большом проекте и возможно будет две одинаковые функции которые делают одно и то же 3 раза в лучшем случае.
                                                                                                                                  Мое «в идеале» выглядит так же как и бесполезная функция «ради дженериков», но ваш код хоть работать будет в отличии от моего )
                                                                                                                                    0

                                                                                                                                    Ну это если функция фильтрует слайсы, то она
                                                                                                                                    "бесполезная". Ну гляньте на примеры из статьи https://blog.golang.org/pipelines, например функцию merge. Как же весло, когда нельзя такую функцию добавить в непонятный пакет, а нужно копипастить по большому проекту!


                                                                                                                                    Мне кажется ваша логика сломалась: с дженериками будет копипаста из-за того, что люди забудут об существующей реализации — это плохо, но без дженериков будет еще больше копипасты — это типо хорошо.

                                                                                                                                      0
                                                                                                                                      Так говоришь как буд-то твой проект только и делает что фильтрует то да се, а затем делает мап/редьюс и после этого добавляет в массив результат который снова надо фильтровать и поехали заново по кругу. Я выше написал и лично я не могу вспомнить где мне реально понадобилась функция которая заменяет 3 строки кода. В го обработка ошибок только занимает 3 строки кода и делается через каждые 5 минут, это никого не волнует, а написать пару раз фильтр на 3 строки за проект это ужасно много и надо обязательно вынести в функцию в пакет, и сделать универсальной ради пары раз использований.
                                                                                                                                        0
                                                                                                                                        это никого не волнует

                                                                                                                                        Это не волнует фанатиков и дураков. Так то народ постоянно стонет и ругается на обработку ошибок в Го (точнее на ее отсутствие). Блин, этож самая популярная тема похейтить сей яызк :D


                                                                                                                                        Я выше написал и лично я не могу вспомнить где мне реально понадобилась функция которая заменяет 3 строки кода.

                                                                                                                                        Ну во-первых, тут три строки. Там три строки… Потихоньку набегает. А во-вторых — а зачем так привязываться к filter? Ведь можно поговорить о более удачных примерах из оригинального пропозала


                                                                                                                                        Там приводят


                                                                                                                                        func Keys(m map[K]V) []K
                                                                                                                                        func Uniq(<-chan T) <-chan T
                                                                                                                                        func Merge(chans ...<-chan T) <-chan T
                                                                                                                                        func SortSlice(data []T, less func(x, y T) bool)

                                                                                                                                        Неужели ни разу не понадобилась фунция Keys?


                                                                                                                                        а написать пару раз фильтр на 3 строки за проект

                                                                                                                                        А, понял. Проекты просто махонькие. Ну тогда норм.

                                                                                                                                          –2
                                                                                                                                          Вот пропозал с ошибками отменили а дженерики добавить собираются.
                                                                                                                                          Нет, мне ни разу не понадобилась функция keys. Я редко работаю с мапами, в основном структуры. Я стараюсь писать код так чтоб не было неоднозначности, без пустых интерфейсов и милиона разных типов, если мне надо работать с int и int64 я выберу int64 для обоих вариантов итд. Да, я понимаю что дженерики кое где упростят жизнь, я пришел в го из TypeScript так что о дженериках не по наслышке знаю, но в го мне они, внезапно, перестали быть нужны. Ибо в 99% я точно знаю с чем работаю, либо у меня интерфейс который делает только то что умеет и больше мне от него ничего не нужно.
                                                                                                                                          зы: нет, не махонькие, к слову.
                                                                                                                                  0
                                                                                                                                  го работает с массивами интерфейсов через одно место

                                                                                                                                  С массивами или слайсами?
                                                                                                            0
                                                                                                            А для чего были добавлены интерфейсы? Нет, понятно, что есть еще и юз.кейс а-ля fmt.Printf, но кто вам сказал, что нельзя использовать интерфейсы для эмуляции дженериков?

                                                                                                            Вот, кстати, пример из стандартной библиотеки:

                                                                                                            type ValueConverter interface {
                                                                                                            	// ConvertValue converts a value to a driver Value.
                                                                                                            	ConvertValue(v interface{}) (Value, error)
                                                                                                            }
                                                                                                            


                                                                                                            Тоже с интерфейсом в качестве параметра.
                                                                                                              +1
                                                                                                              Ну это же отвратительно, если честно. Это почти компилируемый джаваскрипт получается.
                                                                                                                0
                                                                                                                Да. Увы. А другого способа в данный момент нет. Кроме копипаста или генераторов кода.
                                                                                                                0
                                                                                                                Я ведь не говорил что нельзя использовать интерфейсы. Только вот есть ли у вас хоть одна функция которая должна принимать на вход «абсолютно любой тип»? если нет, а я уверен что нет, тогда не надо приводить в пример такое.
                                                                                                                В го в большинстве случаев вы используете типы явно, если вам нужно что-то универсальное, вы можете написать функцию с интерфейсом для универсальной работы (не пустым интерфейсом а нормальным), и затем после работы этой фцнкции привести свои данные назад в нужный тип.
                                                                                                                Вы ведь точно знаете с чем работаете, или нет?
                                                                                                                Или вы из тех людей которые используют дженерики где можно и где нельзя просто потому что «я могу»?
                                                                                                                  +1
                                                                                                                  Вы ведь точно знаете с чем работаете, или нет?

                                                                                                                  Вы писали что-то сложнее простой утилитки когда-нибудь?
                                                                                                                  Не знать конечный тип даже в более строго типизированных языках — сплошь и рядом. От откровенной херни спасают только контракты (дженерики, интерфейсы). В Go в данный момент это карнавал непредсказуемости.

                                                                                                                    0
                                                                                                                    А вы когда нибудь писали код который работает с интерфейсами вместо типов? Внезапно в приложении сложней простой утилитки нужно отвязываться от типов и полагаться на интерфейсы, тогда и большинство проблем исчезнет само собой. Вам просто не важно будет с каким типом вы работаете. Люди в ентерпрайз сегменте так делают с незапамятных времен, специально чтоб делать приложения лучше. И внезапно это стало плохо.
                                                                                                                      0
                                                                                                                      Внезапно в приложении сложней простой утилитки нужно отвязываться от типов и полагаться на интерфейсы, тогда и большинство проблем исчезнет само собой.

                                                                                                                      Вы противопоставляете динамическую диспетчеризацию статической, в то время как они должны сосуществовать. Люди в энтерпрайз сегменте уже досыта наелись ошибками в проде, которых можно было бы избежать, переложив на подгонку типов при компиляции то, что делалось в рантайме. Как пример, боль при переходе с Java 1.4 на 1.5 была невыносимой, но даже крупный и ленивый энтерпрайз рано или поздно в это ввязывался.

                                                                                                                        0
                                                                                                                        А причем тут рантайм? все ошибки проверяются на этапе компиляции при работе с интерфейсами. interface{} — это не работа с интерфейсами, это отстой. Нормальный интерфейс реализует действие. И вы можете принять только те типы которые это действие могу делать. Это нормальная ситуация, это минимум ошибок, это простой и понятный код и это прекрасное разделение реализации от вида. Лисков об этом много говорила.
                                                                                                                        Я прекрасно понимаю что от типов отказаться полностью нельзя и в большинстве случаев (я выше писал) вы работаете с типами которые 100% вы знаете. Но в остальных случаях интерфейсы (нормальные а не пустые) заменяют дженерики практически полностью. В моем случае я лишь единожды столкнулся с ситуацией где мне нужны были дженерики, это очень маленький процент всего кода и в математике такие величины игнорируются, в тот раз мне было плохо, но я пережил.
                                                                                                                          0

                                                                                                                          Рантайм будет причем, когда упадет на проде. И если вы знаете, что использовать interface{} не самая лучшая идея, то тот, кто будет ваш код использовать, может этого не знать.

                                                                                                                    +1
                                                                                                                    Только вот есть ли у вас хоть одна функция которая должна принимать на вход «абсолютно любой тип»

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

                                                                                                                    И, кстати, в статье приводится отличный пример на счет Min/Max. Сейчас это сделано с тем, что math.Max() принимает float64. Т.е. все остальные типы вас вынуждают приводить к float64. Надо ли объяснять, что это не эффективно? Типа, если вам нужно эффективно, то пишите свою функцию Max? А всё из-за отсутствия дженериков…

                                                                                                            0

                                                                                                            Так если подставить интерфейс в том месте, где подставляется контракт, то его можно будет в compile time посчитать.
                                                                                                            Да и в конце концов, обобщения все равно ломают совместимость, можно было бы и интерфейсы доработать.

                                                                                                              0
                                                                                                              Так если подставить интерфейс в том месте, где подставляется контракт, то его можно будет в compile time посчитать.

                                                                                                              И как это сделать?

                                                                                                              обобщения все равно ломают совместимость

                                                                                                              А как ломается совместимость? Какой старый код не будет работать?
                                                                                                                0

                                                                                                                А посмотреть как в других языках эту проблему решили? Просто пишешь, что у тебя аргумент это обобщенный тип, реализующий определенные интерфейсы. В предложении тоже самое сказано про контракты, в этом смысле они просто дублируют интерфейсы.
                                                                                                                Я уже молчу про то, что например для сравнения в контракте нужно исчерпывающий список типов писать, а как добавить новые типы в контракт сравнения не понятно.
                                                                                                                В этой новости не было про обратную совместимость, но я помню было обсуждение про go 2.0 и там вроде бы решили, что можно ее немного и сломать

                                                                                                                  0
                                                                                                                  А посмотреть как в других языках эту проблему решили?


                                                                                                                  По разному. Где-то шаблонами в compile-time (C++, бесплатные абстракции и все вот это), где-то резолвом в рантайме (привет Java и type-erasure).

                                                                                                                  «Нельзя просто так вот взять и перенести в свой язык фичу из другого языка». Ну или можно, но это PHP получится.

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


                                                                                                                  В этом смысле дублируют, в других — нет. Просто контракт и интерфейс — разные сущности. Интерфейс в рантайме существует, а контракт — сущность сугубо compile-time'овая, хинт для компилятора, а потому в рантайме бесплатная.

                                                                                                                  Я уже молчу про то, что например для сравнения в контракте нужно исчерпывающий список типов писать


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

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

                                                                                                                  Я уже молчу про то, что например для сравнения в контракте нужно исчерпывающий список типов писать,


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

                                                                                                                  а как добавить новые типы в контракт сравнения не понятно.


                                                                                                                  Через запятую же)))

                                                                                                                  В этой новости не было про обратную совместимость, но я помню было обсуждение про go 2.0 и там вроде бы решили, что можно ее немного и сломать


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

                                                                                                                  Поломать собирались обработкой ошибок, но ее откатили.
                                                                                                                    0
                                                                                                                    В этом смысле дублируют, в других — нет. Просто контракт и интерфейс — разные сущности. Интерфейс в рантайме существует, а контракт — сущность сугубо compile-time'овая, хинт для компилятора, а потому в рантайме бесплатная.

                                                                                                                    Ну грубо говоря и контракт и интерфейс требуют от типа некоторых свойств, читай наличия методов, разница, как я понимаю, лишь в том, что интерфейс неявно реализуется для типов (утиная типизация и все дела), а в контракте ты пишешь для каких типов он поддерживается.
                                                                                                                    Но никто не мешает ввести и для контрактов такую же утиную типизацию, да и в целом может лучше просто отказаться от утиной типизации в интерфейсах и реализовывать их явно? Или наоборот, оставить интерфейсы, но пометить их как deprecated, а контракты использовать и для статической и динамической диспетчеризации?


                                                                                                                    Через запятую же)))

                                                                                                                    Ну хорошо, у меня есть свой тип, я хочу для него добавить поддержку контракта из чужого пакета, я смогу это сделать? Или скажем иначе, я могу определить контракт Sequence для своего типа?

                                                                                                                      +1
                                                                                                                      Конечно же от утиной типизации надо отказываться, это ужасное решение, но сейчас это делать, боюсь, поздно и несподручно.
                                                                                                                        +1
                                                                                                                        Когда вы говорите об интерфейсах вместо контрактов, то как это соотносится со скалярными типами? Они-то никакие интерфейсы не реализуют… Или вы предлагаете, как в Ruby считать, что [почти] всё — объект? Да и как быть со слайсами? Скажем, []uint64 и []int64. Как их объединить одним интерфейсом? Писать wrapper?
                                                                                                                          +1

                                                                                                                          А что мешает скалярному типу реализовывать интерфейс сравнения на большее меньше?

                                                                                                                          +1
                                                                                                                          Ну грубо говоря и контракт и интерфейс требуют от типа некоторых свойств, читай наличия методов, разница, как я понимаю, лишь в том, что интерфейс неявно реализуется для типов (утиная типизация и все дела), а в контракте ты пишешь для каких типов он поддерживается.


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

                                                                                                                          Контракт — набор ограничений, проверок в компайл-тайме, гарантирующих, что оно «соберется» (с очевидной гарантией, что будет работать, т.к. ничего «приводиться» в рантайме не будет).

                                                                                                                          Т.е. интерфейс — это тип, со всеми вытекающими. Контракт — набор ограничений.

                                                                                                                          Но никто не мешает ввести и для контрактов такую же утиную типизацию, да и в целом может лучше просто отказаться от утиной типизации в интерфейсах и реализовывать их явно? Или наоборот, оставить интерфейсы, но пометить их как deprecated, а контракты использовать и для статической и динамической диспетчеризации?


                                                                                                                          Не надо, ну пожалуйста! Не складывайте мух с котлетами в одну кучу, оно будет:
                                                                                                                          а) неочевидно;
                                                                                                                          б) вы сами хрен разберетесь, в каком случае и при каком положении звезд оно будет резолвиться в рантайме, а когда работать набором ограничений компиляции;
                                                                                                                          в) поломает все к хренам, включая обратную совместимость;
                                                                                                                          г) породит больше проблем, чем решит.

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


                                                                                                                          Вопросы говорят сами за себя. Контракт нельзя поддерживать, ему можно соответствовать либо не соответствовать. Контракт — набор ограничений.

                                                                                                                          Или скажем иначе, я могу определить контракт Sequence для своего типа?


                                                                                                                          Кто ж вам запретит?)))
                                                                                                                            0

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

                                                                                                                              0
                                                                                                                              Вообще контракт это класс типов на самом деле, а интерфейс это тип с динамической диспетчеризацией.


                                                                                                                              Ну вот, ну сами же все правильно говорите: интерфейс — это тип, контракт — нет. Интерфейс — динамическая диспетчеризация, контракт — статическая. Осталось понять, что же между ними общего…
                                                                                                                                0

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

                                                                                                                                  –1
                                                                                                                                  Общего между ними это то, что это диспетчеризация


                                                                                                                                  Интерфейс — диспетчеризация, без вопросов. Контракт — нет.

                                                                                                                                  и то, что это ограничения, при этом ограничения на интерфейсы они строже, чем на контракты.


                                                                                                                                  Интерфейс — это не ограничения. Это ссылочный тип. А контракт — набор ограничений. Разница в том, что вы можете получить экземпляр интерфейса, а экземпляр контракта — нет, ибо не нужно.

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


                                                                                                                                  Неправда же. Множества не пересекаются. Интерфейс — это ссылочный тип, описываемый как набор методов, которые у этого типа есть. Контракт — не тип, и методов у него нет. Контракт — это список ограничений, в котором говорится, что на место, ограниченное контрактом, можно подставить любой тип, соответствующий этому списку.

                                                                                                                                  Понимаете, есть ссылочный тип (интерфейс), и есть набор ограничений (которым в некоторых случаях может соответствовать интерфейс).

                                                                                                                                  Это примерно как есть автомобиль и есть утвержденные технические требования заказчика, не позволяющие перевозить крупногабаритные грузы на автомобиле с грузоподъемностью менее 3 тонн. Что общего между автомобилем и подписанной с двух сторон документацией? В обоих встречается слово «автомобиль»?
                                                                                                                                    0

                                                                                                                                    Я же про СЕМАНТИКУ говорю, а не про реализацию.

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

                                                                                                                                      Интерфейс — это тип, т.е. существующий в рантайме указатель на некую структуру + таблица методов, определенных на этой структуре. Контракт — это хинт для компилятора, в рантайме не существующий.

                                                                                                                                      type someInterface interface {...}
                                                                                                                                      func interfaceFunc(val someInterface) {...}
                                                                                                                                      var val1 someInterfaceImplementation1
                                                                                                                                      var val2 someInterfaceImplementation2
                                                                                                                                      interfaceFunc(val1)
                                                                                                                                      interfaceFunc(val2)


                                                                                                                                      Что происходит в этом коде? Что вы увидите в рантайме? Одна функция interfaceFunc, в которой val на входе приводится к someInterface (т.е. на вход приходит указатель на саму структуру + таблица методов someInterface, сгенерированная из методов, определенных над исходным типом).

                                                                                                                                      func genericFunc (type T) (val T) {...}
                                                                                                                                      var val1 int
                                                                                                                                      var val2 string
                                                                                                                                      genericFunc(val1)
                                                                                                                                      genericFunc(val2)


                                                                                                                                      Что мы видим здесь? Здесь в рантайме мы имеем две функции genericFunc, одна из которых принимает int, а вторая — string. И все, и никакой магии.

                                                                                                                                      Т.е. интерфейс и контракт в качестве ограничений для дженериков — совершенно не пересекающиеся множества с точки зрения реализации в Go. Зачем вы хотите, чтобы они внешне выглядели одинаково, я не совсем понимаю.
                                                                                                                                    +2
                                                                                                                                    Я думаю qrKot пытается сказать что контракты – это условия, в терминологии Rust:

                                                                                                                                    where T: Foo<U>, U: Copy

                                                                                                                                    а не сами типажи/интерфейсы как таковые.

                                                                                                                                    В таком смысле контракты и интерфейсы это действительно не пересекающиеся множества. Правда, почему реализация контрактов и интерфейсов не должна происходить через один и тот же механизм (как типажи в Rust) я все равно не понимаю.
                                                                                                                                      0
                                                                                                                                      контракты – это условия, в терминологии Rust
                                                                                                                                      а не сами типажи/интерфейсы как таковые.


                                                                                                                                      Так точно!

                                                                                                                                      В таком смысле контракты и интерфейсы это действительно не пересекающиеся множества.


                                                                                                                                      В точку!

                                                                                                                                      Правда, почему реализация контрактов и интерфейсов не должна происходить через один и тот же механизм (как типажи в Rust) я все равно не понимаю.


                                                                                                                                      Усложнение семантики языка, очевидно.

                                                                                                                                      Сейчас есть interface:
                                                                                                                                      1. это тип
                                                                                                                                      2. это тупо-в-лоб набор методов

                                                                                                                                      Вводится контракт:
                                                                                                                                      1. это набор ограничений/хинт для компилятора
                                                                                                                                      2. может содержать набор методов, перечисление типов, уточнение определенных для типа операторов и кучу всего, чего еще не придумали
                                                                                                                                      3. может быть определен над некоторым набором типов, (т.е. (type T, E), где T относится к сравнимым типам, E реализовывает интерфейс Stringer, и T может быть приведено к E)

                                                                                                                                      Попробуем объединить:
                                                                                                                                      1. Может быть типом, а может быть хинтом компилятора
                                                                                                                                      2. Может быть определен над некоторым набором типов (неясно, что это дает в семантике интерфейсов, и как оно должно выглядеть)
                                                                                                                                      3. Может содержать набор методов, полей, перечисление других типов, и прочие указания того, что может быть важно в каком-то контексте
                                                                                                                                      4. Может быть рантайм-сущностью, а может зарезолвиться в компайл-тайме в статически собранный набор требуемых методов, в зависимости от использованных параметров, положения звезд, версии компилятора, операционной системы и дня недели.

                                                                                                                                      Ну, имхо, слишком много геморроя на пустом месте (т.е. тупо заради того, чтобы не вводить новое ключевое слово в язык) + миллион человеко-часов для того, чтобы сделать компилятор языка более умным/медленным/сложным, чтобы он смог, наконец, сравниться по сложности и скорости работы с компилятором C++.

                                                                                                                                      Ну, не знаю, мне кажется, что добавление еще одной относительно простой конструкции в язык, не трогая то, что уже и так работает — оно, может, и не самое научно-обоснованное и гениально-правильное решение, но оно — однозначно наименьшее зло.
                                                                                                                      0
                                                                                                                      Так если подставить интерфейс в том месте, где подставляется контракт, то его можно будет в compile time посчитать.


                                                                                                                      Как раз наоборот же. Интерфейс не гарантирует возможность посчитать в compile-time. interface{}, т.е. пустой интерфейс — это как раз сущность рантайма, его в compile-time «посчитать» нельзя. Плюс приведения интерфейсов и т.д. выполняются в рантайме.

                                                                                                                      Контракт же гарантировано считается в compile-time, в том и задумка.