Как стать автором
Обновить

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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

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


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

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

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


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

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


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


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

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

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


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

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


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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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


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

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

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


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


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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

или


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

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

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


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


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

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


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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Вот вам пример из раста, который хотел выглядеть как 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) > ()
Выглядит, как гниль в спецификации языка.
Какой-то C# даже близко не имеет таких проблем.
Извините, кажется у вас C# прогнил:

image

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

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

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

bool b = (f as F) < a;

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

bool b = f as F < a;

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

bool b = f as F > a;

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

Ну вот. Знаки сравнения в языке обрабатываются по разному. Дыра в спецификации!
Это из спека 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.


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

Вы ведь понимаете что анонимная функция — функция которую вы только что написали? Мап это не обязательно метод, это еще может быть 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#
Ну и возвращается не только корректные данные, но и тип правильный, который далее можно использовать:

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

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


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

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


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

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

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


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

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

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

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

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


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

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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


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

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

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


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

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


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

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


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

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

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


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

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

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

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

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


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

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

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

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


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

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


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

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


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

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

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


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

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

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


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

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


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

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


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

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

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

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

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

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

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. Зачем вы хотите, чтобы они внешне выглядели одинаково, я не совсем понимаю.
Я думаю qrKot пытается сказать что контракты – это условия, в терминологии Rust:

where T: Foo<U>, U: Copy

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

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


Так точно!

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


В точку!

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


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

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

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

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

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

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


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

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

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

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


Зачем чинить то, что не сломано? Зачем заставлять интерфейсы быть тем, чем они не являются?

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

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

Вы где-то "контракты" потеряли и теперь сравниваете тёплое с мягким.


посмотрите на другие языки, там тоже не смешивают

Ок, смотрим на другие языки:


interface User {
    name : string
    age : number
}

function olderUserInterface( a : User , b : User ) {
    return b.age > a.age ? b : a
}

function olderUserGeneric< U extends User >( a : U , b : U ) {
    return b.age > a.age ? b : a
}
Вы где-то «контракты» потеряли и теперь сравниваете тёплое с мягким.


Про «контракты потерял» — согласен, а тёплое с мягким — не совсем. Корректная фраза: «Контракты и интерфейсы — сущности ортогональные.» Прошу прощения, опечатался, очень спешил.

А теперь о сути вашего примера:

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

Ну и если уж прокомментировать ваше " И после этого они говорят про минимализм и ортогональность."

Ну да, а еще говорят «явное лучше неявного» и кучу других достаточно мудрых вещей.

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

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

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

User — интерфейс. Вы не торопитесь, вас никто не торопит.


явное лучше неявного

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


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


Опять же, пример из других языков:


// рантайм сущность
class User {}

// компайлтайм сущность унаследованная от рантайм сущности
interface CoolUser implements User {}
User — интерфейс.

class User {}


Вы уж это… Определитесь, что ли… User — это интерфейс или класс…

interface CoolUser implements User {}

Чуть выше:
U extends User


implements таки или extends? Потому что наследование и реализация интерфейса — несколько разные механизмы…

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


// рантайм сущность
class User {}

// компайлтайм сущность унаследованная от рантайм сущности
interface CoolUser implements User {}


Кхм, класс — рантайм сущность, а интерфейс — компайлтайм? Ну, рад за вас…

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


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

Ну и нафига? Чтобы не запоминать целое ключевое слово?
Вы уж это… Определитесь, что ли… User — это интерфейс или класс…

Фу, какая некрасивая подтасовка.


implements таки или extends?

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


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

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


Вы поломаете то, что уже написано.

Ничего там не поломается. Вот чего вы фантазируете?


Продолжать с вами дискуссию мне не интересно, удачи.

Фу, какая некрасивая подтасовка.


Не понял вашего выпада. Я привел два ваших высказывания, в одном из которых вы объявляете User классом, а в другом — интерфейсом, и предложил определиться, в целях конструктивности дальшейшей дискуссии, чем же мы будем считать User дальше. Вы обвинили меня в подтасовке… Странный вы.

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


Кхм, как это без разницы? Implements — «реализует», т.е. «является экземпляром». Extends — «расширяет», т.е. «является наследником». Мне кажется, разница достаточно существенна.

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


type Needed interface {
StringMethod() string
IntMethod() int
}


Где отрезолвится?

// Функция уровня пакета
func PackageLevelFunction(item Needed) bool { ... }


Я сейчас дженерик-функцию объявил, или просто интерфейс в сигнатуре указал? К этому Needed кастовать можно, или он в компайл-тайме зарезолвился?

Ничего там не поломается. Вот чего вы фантазируете?


type Stringer interface поломается, например…

Продолжать с вами дискуссию мне не интересно, удачи.


И вам не хворать…
В Rust трейты можно и как динамические интерфейсы использовать и как контракты в генерик методах.

И это была настолько неудачная идея, что от нее спасались как могли.

Не могли бы вы как-то подкрепить свое мнение? Вы дали ссылку на stackoverflow где человек не совсем понимает что он делает и на RFC с предложением улучшения синтаксиса.

В RFC есть отдельный раздел "мотивация" с причинами введения аж целого резервированного слова для решения проблемы разделения типажей на статические и динамические контракты. Если мало — есть обсуждения.


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

RFC мотивиран улучшением наглядности, разделение на static и dynamic dispatch никак не изменилось.
То, что человек не понимает, что он делает, и является основной проблемой
Именно, и смею предположить что виноват здесь не язык, а недостаток понимания.

Из приведенного вами вопроса на stackoverflow. Человек хочет хранить поле типа MyTrait в своей структуре. Вроде бы понятная потребность, вот только как это должно работать? Тип структуры должен быть параметризирован типом и во время компиляции она должна мономорфизироваться для любого типа T реализующего MyTrait? Или на самом деле мы хотим в структуре хранить виртуальную таблицу методов этого типажа? Проблема именно в том что человек не до конца понимает что он в действительности хочет.
разделение на static и dynamic dispatch никак не изменилось

Я именно это и сказал — главная проблема не решена. Непонимание ее — просто отправная точка для хоть каких-то попыток ее решить или смягчить. Потому в итоге расплодилось синтаксиса на Trait (легаси), dyn Trait (чисто динамика), impl Trait (чисто статика). Попробую поискать обсуждение планов на типажи, поделюсь если найду (сходу не находится) — вроде как там проскакивала депрекация общего типажа.

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

Bare синтаксис существовал когда impl Trait еще не реализовали, а значит и не было с чем его путать. Когда это сделали, решили провести унификацию: impl Trait vs dyn Trait. Но старый синтаксис пока оставили, чтобы не нарушать обратную совместимость.

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

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

Где это открыто называется ошибкой архитектуры, я в официальных источниках пока не нашел, только на SO. Но скорость, с которой по dyn/impl принимались решения, косвенно говорит в пользу этой версии.

в черновике описано почему отклонили:
github.com/golang/proposal/blob/master/design/go2draft-contracts.md#why-not-use-interfaces-instead-of-contracts

Вообщем там список из семи часто спрашиваемых отклоненных идей «why not»
AloneCoder Почему статья помечена как перевод? Оригинал длинней раз в 10. Вы выкинули весь негатив и сомнения по поводу дженериков, оставив только их «плюсы». Увеличится время исполнения или компиляции. Усложнится чтение и понимание кода. Дженерики начнут пихать всюду. Очень много нововведений в пропорсал. Очень мал юзкейс применения дженериков. Не пропорциональное усложнение языка. Я против в данном виде. Если не согласны — прочтите оригинал для начала: github.com/golang/proposal/blob/master/design/go2draft-contracts.md
Это перевод статьи (ткнуть надо в «Автор оригинала...») под заголовком, а не драфта.
Когда я начал Интересоваться golang, больше всего влекла идея простоты и выразительноси, спустя где-то год повернулась возможность написать рест апи на го, до этого — ни строчки кода…

Сейчас более 2х лет свыше 90% написанного кода мной — на Go.

Последний год все чаще приходиться работать с чужим кодом, и все чаще меня бросает в ужас/ступор/холодный пот…

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

Как можно настолько похерить идею лаконичности и выразительности?

А вы говорите дженертков не хватает…

В го приходят многие из интерпритируемых языков, и многим из многих вообще мало известно о природе типов…

А потом нам с ихним кодом работать.

Простите, накипело…

Отнедавна участвую в проекте, пред.разрабы форкнулись от опенсорс, накрутили поверху своих ''фич'', другой разраб после них начал добавлять новые ''фичи'' и заодно учить Go… и это подобие с кучей сторонних либ, миллионом интерфейсов, странными реализациями сервисовнутртлибной имплементацией (map[string] func(interface{} )interface{} ) и т.п… сейчас стонет у меня на столе…

Пожалуйста, не нужно дженертков…

Пригорело, простите…
То, что вы видели много говнокода на Go, вообще никак не значит, что языку не нужно развитие в сторону дженериков. Это не нормально, когда ты вынужден копипастить (или генерить) кучу одинаковых функций с разными типами аргументов, либо использовать рефлексию, чтобы выразить что-то общее. Это не нормально, когда перед каждой функцией, которая принимает []interface{}, а у тебя []string или []int, нужно гонять цикл по этому массиву и просто перегонять значения из одного типа в другой.

И еще касательно плохого кода. Я около года всего пишу на Go, но за это время о каких-то конкретных правилах и best-practice я слышал очень поверхностно. Я не видел, чтобы кто-то использовал какой-то фреймворк, который располагает к определенному стилю написания кода. Максимум, что я встречал в проектах — это куча линтеров. А касательно принципов — кучку высказываний аля «Возвращайте структуры, принимайте интерфейсы» по поводу которых написана тонна статей с обоснованием, но в итоге потом оказывается, что это правило не всегда актуально и не работает в куче случаев. Поэтому нет ничего странного, что многие, приходя в язык, стараются натянуть сову на глобус, не у всех хватает смелости признать, что его прошлый опыт не релевантен и нужно писать по-другому, но интересно, что при этом у новичков часто нет какого-то относительно «правильного» примера.

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

И да, если вы знаете какой-то свод правил или серию статей о «правильном» коде на Go, я буду признателен, если вы мне их посоветуете.

Вот интересно, кстати. Многие говорят про го-вэй и про то, что не нужно тянуть подходы из других языков.
Это понятно, но можете привести какие-то примеры хорошего го-вейного продуктивного кода (не стандартные либы), который прям вот 100% отражает принципы го?
Сам уже не первый год пишу на го, но как дело доходит до серьезной логики, все равно получаются классы, фабрики, DI и интерфейсы (ну хоть без наследования).

Я долго искал качественный код на Go, думал начать на нем писать из материальных соображений. Задавал вопросы в форумах, искал репозитории, статьи и пр. Так и не нашел.

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

Нарушены буквально все принципы того же Clean Code.
'6' <= f[1][0] && f[1][0] <= '8'
Это просто квинтэссенция всего Гоу какая-то.
Начал изучать Go не так давно…
И постоянно как 6-ти классник над «многочленом» хихикаю над:
Пусть у нас есть слайсспайс целых чисел.

Ни чего не могу с собой поделать.
НЛО прилетело и опубликовало эту надпись здесь

А что вас заставляет писать на Go? Вроде же вполне естественно с накоплением опыта желать выражать свои мысли на более выразительном языке, а не на менее выразительном…
Мне до сих пор иногда кажется, что кто-нибудь из Google выскочит с большим плакатом РОЗЫГРЫШ.

НЛО прилетело и опубликовало эту надпись здесь

Для любопытства вопросов нет, я тоже Go ради любопытства изучил и даже 1 сервис на нём написал. Но постоянно писать на таком урезанном и во многом непоследовательном языке — хз… Зачем лишать себя удовольствия от программирования?
Если только ради сильного интереса к конкретной сфере, где он получил распространение. Этот аргумент действительно выглядит убедительно :-)

Заставляет писать на Gо тот факт, что все проекты CNCF (Cloud Native Computing Foundation) написаны на нем. Так что хочешь не хочешь, а надо писать на нем (например для контрибуции)
программирование с дженериками позволяет представлять функции и структуры данных в виде дженериков, исключая типы.

Нельзя объяснять термин используя тот же самый термин кроме термина «рекурсия».
Программирование с дженериками позволяет акцентировать внимание на алгоритме, абстрагируясь от конкретного типа.
Вообщем, люди разделились на два лагеря:
— одни перешли в go из типизированных языков(где есть дженерики). Они считают, что дженерики нужны.
— вторые пришли в go из языков без строгой типизации(где дженериков никогда и не было). Они считают, что дженерики не нужны.
Fight!
А зачем в языках без строгой типизации дженерики? Нет, ну вот я знаю, что для PHP есть RFC с предложением дженериков. Но только там это появилось от того, что php начал двигаться в сторону строгой типизации.
Всё верно, я о том кто к чему больше привык просто.
Так нет, речь о том, что, скажем, универсальную функцию max можно сделать как в языке со строгой оптимизацией и без дженериков, так и на языке со строгой типизацией и дженериками. А вот на языке со строгой типизацией без дженериков универсальную функцию сделать можно, но больно.
Для реверса, вполне можно написать такую функцию:

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


Или это не так?)
Так, но перед каждым использованием ее будете гонять цикл для преобразования []string или []int или любого другого массива в []interface{}. А потом обратно
Либо же надо сделать входной параметр просто interface{}, но тогда придется в функции использовать рефлексию и проверять, слайс ли пришел. Что лучше? Не знаю. И я не об эффективности…
Для реверса, вполне можно написать такую функцию:
Или это не так?)
Что, если нам надо написать функцию, которая возвращает новый массив, а не изменяет старый — как вы это сделаете?
Для меня golang прекрасен именно отсутствием генериков и гибкими интерфейсами. Эти два подхода замечательно заменяют друг друга. Ты хочешь обязать какой-то объект обладать какими-то свойствами. Опиши это в интерфейсе и передавай в функцию свой интерфейс. Все. Не надо шаблон. Функция будет работать одинаково для всех типов, которые подходят под интерфейс. Я пишу часто на C++ и в моей жизни хватает шаблонов. Пожалуй их даже слишком много и Golang как глоток свежего воздуха с подохдом интерфейсов.

Если реализовывать два подохда одновременно. Будет каша и путаница.
Так не надо шаблонов, надо дженерики.

А «гибкие интерфейсы» потом вам светят несуразными паниками в рантайме (и хорошо, если девелоперском рантайме, а не в продакшне).
Погодите, а откуда паника появится? Если на этапе компилации уже будет все проверено, чтобы входные типы умели делать то, что требует интерфейс.
Если на этапе компилации уже будет все проверено, чтобы входные типы умели делать то, что требует интерфейс.

Проверено — на основании чего?

На основании функций, которые тип реализует, а интерфейс требует?

package main

import "fmt"
import "math"

type geometry interface {
	area() float64
	perim() float64
}
type circle struct {
	radius float64
}
func (c circle) area() float64 {
	return math.Pi * c.radius * c.radius
}
// func (c circle) perim() float64 {
// 	return 2 * math.Pi * c.radius
// }
func measure(g geometry) {
	fmt.Println(g)
	fmt.Println(g.area())
	fmt.Println(g.perim())
}
func main() {
	c := circle{radius: 5}
	measure(c)
}

Компилятор возвращает ошибку:
test.go:29:9: cannot use c (type circle) as type geometry in argument to measure:
circle does not implement geometry (missing perim method)

Научите, как сделать панику в рантайме.

Банально.
Вы можете возразить — так здесь же все очевидно, не надо кастить что угодно к чему попало! В том и дело, что на сложных проектах совсем не очевидно, откуда что пришло и к чему приводится. Вот похожие касты возникают, когда на рантайм перекладывают обязанность компилятора — если в функции ожидается square и ничего больше, для circle этой функции вообще не должно существовать. Да, можно воткнуть проверку типа рефлексией, но это процессорное время и лишние несколько строк кода.

Может быть я не работал на сложных проектах на Go. Но работал на сложных проектах на C++. Где стрелять себе в ногу проще по многим причинам. И если есть возможность не писать хреновый код, его не надо писать. Могу понять студенческие поделки, но в продакшене такого быть не должно. Язык сам дает инструменты, чтобы проверять, что все норм. Почему бы ими не воспользоваться?

Тут у вас есть возможность проверить, успешный ли каст или нет. Добавили проверку — все работает.

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

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

Ну добавите вы проверку. Сделали такую проверку в рантайме, тип не тот, паники нету… Но что дальше? Напечатать в лог "Unsupported!!" и продолжаете выполнение? Т.е. в вашем понимании это "все работает"?

А в чем собственно проблема? Вы же сейчас прочитали абстрактный кусок кода, сделали какие-то выводы и спрашиваете у меня верны ли эти выводы.

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

Но это все риторика о том как обрабатывать ошибки.

Усложнение концепции языка не защитит от ошибок, а наоборот сделает код сложнее и ошибок станет больше. Несмотря на благие намерения. Стандартные библиотеки go показали, как на нем можно решать прикладные задачи и без дженериков.
Подозреваю, что похожие касты возникают, когда надо побыстрее накодить костыль (говорю это без осуждения, самому приходится заниматься костыльным программированием из-за нехватки времени). По хорошему надо добавить добавить метод «print» в интерфейс «geometry». Язык тут ни при чем. Вопрос цены изменения кода. Если этот интерфейс реализует не три класса, а три тысячи, то костыль с кастом будет быстрее закодировать. Но этот костыль потом с большой вероятностью воткнется в спину, когда через некоторое время нужно будет добавить три тысячи первый класс, реализующий интерфейс «geometry», а человек, который добавляет новый класс, не в курсе про этот костыль.

Ну и как, например, "по хорошему" обойтись без кастов из interface{} при использовании, скажем, модуля heap? Научите плз.

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

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

Прошу прощения, не соображу, а как тогда будет выглядеть универсальная функция Sort, работающая абсолютно с любым типом, и встроенным, и кастомным?

Мне синтаксис как то не очень. А разве нельзя просто ввести тип type, то есть разрешить декларации вроде
type Gene type
и получить все плюшки Hindley–Milner не меняя синтакис, а прямо в существующем?
То есть чтобы понять, является ли функция обобщённой, надо будет бегать по декларациям типов аргументов и проверять, не занесло ли туда type type? Читаемость страдает, как минимум. Каково это с точки зрения парсинга — я тоже не знаю.
Дженерикам — да, но не любой ценой. А данный пропозал, на мой личный вкус (6 лет коммерческой разработки на нём), тяжеловат, увы (
Мне кажется Вы фильм Довод пересмотрели. У Вас одновременно инкремент и декремент :)
func ReverseInts(s []int) {
    first := 0
    last := len(s)
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий