Комментарии 250
FYI: Термин "Дженерики" на русский переводится как "Обобщения".
Потому что не нужно переводить на русский интернационалтные термины хорошо понятные любому программисту
«Дженерики» — слово, появившееся от отсутствия образования. Придумали его те, кто учился не универистетах, а в смузишных и не знают, что есть слово «обобщения», придуманное ещё до их рождения.
А «функции» — это исконно русское слово, вестимо?
Вы путаете литературный перевод и профессиональный сленг/терминологию. Это разные вещи.
Возьмите различные области — юриспруденцию, короблестроение, шахтерское дело, металлургию. Огромное количество терминов называются так, как они называются, хотя имеют перевод на русский язык. Именно потому, что это термины, и люди, оперируя ими, говорят о профессиональных сущностях, а не об общеупотребительных.
В ИТ то же самое.
Для справки: «стейдижнг» = «отладочная среда» (неудобно), «продакшн» = «продуктовая среда» (неудобно, мы говорим «прод»).
— этот нехороший человек предаст нас при первой опасности
Нет уж увольте, хватит засорять русский язык словами которых там отродясь не было, и появляются они там от безграмотности. Если бы вы, хоть одну книгу открыли на русском языке, по данной теме, то ужаснулись бы всем тем обобщениям, кортежам, спискам, ссылочным значениям и другим из ряда вон выходящим терминам использующимся в наших академических кругах. Мы не пигмеи со словарным запасом из 6 слов, не нужно захламлять наш язык всяким мусором, тем более что в русском языке все эти термины уже давно есть.
Кстати, по поводу «отродясь не было»:
Одно дело вливать в язык редкие слова которых не было до этого в языке, типа шлагбаум, штраф, академия. А другое дело по малограмотности начинать спикать как ньюбайный бушмен, который тайпает на кейбоарде, релизя в продакшен что слайсает лоафы брэда шарповым найфом.
Если это новый русский язык, потому что большинству так удобнее, я на таком суржике обобщатся не намерен, а люди которые этим занимаются, малограмотны и не знают ни родного языка и не познали красоты и лаконичности французского и/или английского.
Мое оценочное суждение.
Простите, а как вы произносите "звонит" и имеет ли для вас какое-то значение постановка ударения в данном слове?
ЗЫ бесят порой "борцуны" за чистоту языка, когда сами грамотно писать/говорить не способны.
ЗЗЫ а ещё в добавок к коментарию Сайзыймена — у вас ещё и с литературной корректностью проблемы (не согласованное предложение "Моё оценочное суждение" — пример согласованного "Это — моё оценочное суждение", такое упрощение корректно только в устной разговорной речи). И ё вы не используете, нарушая правила русского языка.
такое упрощение корректно только в устной разговорной речи
Что-то я такого правила не припомню, если на то пошло то там глагола не хватает. И это как-бы ответ на комментарий.
И ё вы не используете
ё во многих случаях необязательна к использованию. Да и если честно не знаю где на раскладке она есть(QWERTY без кириллицы). Вы историю, почему она стала обязательна к написанию в 1942г, знаете?
За опечатки и ошибки можно упрекнуть кого угодно.
Я не скрываю того факта, что из-за отсутствия многолетней практики письменного русского языка, я допускаю некоторые ошибки. Это все вопрос практики. Я никогда не претендовал на роль хранителя русской орфографии. Опечатками и отсутствием запятых языка не испортить. А вот тупым перениманием импортных терминов, можно и даже очень эффективно превратить, русский язык в подобие англо-суржика.
Я никогда не претендовал на роль хранителя русской орфографии. Опечатками и отсутствием запятых языка не испортить. А вот тупым перениманием импортных терминов, можно и даже очень эффективно превратить, русский язык в подобие англо-суржика.
Ну тогда с словарь языка тоже хранить не надо. Нежизнеспособные заимствования — умрут сами. Жизнеспособные — выживут несмотря на десятки "исторически имеющихся" аналогов. Всяческие опечатки, орфографические ошибки — считай новое слово (некоторые слова меняют написание в ходе исторического процесса языка!). Только это новое слово ещё хуже заимствованного. Пунктуация: не справляетесь со сложными предложениями — пишите простые. Вот просто ведь?! Но почему-то вам хочется использовать глубину языка, но наплевать на корректное его оформление. А это compile time error же!
ё во многих случаях необязательна к использованию. Да и если честно не знаю где на раскладке она есть(QWERTY без кириллицы). Вы историю, почему она стала обязательна к написанию в 1942г, знаете?
Ё "необязательна" только в типографике (чтоб этим советским упрощателям в аду гореть… нет, чтобы головой сперва подумать, потом уже резать "ненужные" буквы). Если вы изучали русский язык в школе, если сдавали какие-нибудь экзамены по русскому языку, то начиная с 90х обязательность корректного использования ё точно вернули. Более того — вернули-то раньше, я просто не задавался вопросом, когда именно. Для кверти-клавиатуры в ЙЦУКЕН-раскладке ё располагается вот здесь: `~.
Что-то я такого правила не припомню, если на то пошло то там глагола не хватает. И это как-бы ответ на комментарий.
Глагол в целой куче случаев можно опускать — это корректно с точки зрения литературного языка, а вот несогласованное предложение, стоящее отдельным абзацем, у вас было. Пример, как его согласовать, я вам написал.
Наверное вам не повезло обучатся в наших ВШ и работать с коллегами которые там обучались.
переводить на русский термин, который давным давно используется повсеместно.
Не нужно ничего переводить, значение обобщения было в русском языке всегда(ну или почти, язык же не стоит на месте), вы просто о нем не знали, только и всего.
Я живу на западе и работаю на британскую контору, но никаких проблем с общением с коллегами не испытываю и даже в мыслях никакие "дженерики" не появляются. Как с ВШ осталось, шаблоны проектирования, обобщения итд, так оно и есть, все остальное от малограмотности.
tsya.ru, запятая после «Наверное», запятая перед придаточным в сложноподчинённом предложении.
вы совершаете многовато детских ошибок. :)
запятая после «Наверное», запятая перед придаточным в сложноподчинённом предложении.
Хм, пунктуация в русском языке, тем более, в вышеописанных случаях никогда не являлась делом детским. Вы без вашего сервиса навряд ли смогли бы найти такие ошибки. Я по-русски не пишу уже много лет, все как-то по фр или по английски, а у них с пунктуацией намного проще. Есть все-таки плохое влияние.
bolknote.ru/selected
Говорить «дженерики» — имхо, то же самое, что говорить «чай матЭ» («матЭ» по-испански — «убил», а трава называется правильно «йЕрба мАтэ») или «ты какой чай пьёшь: чёрный или зелёный?» («я люблю разный чай — от гуандунских улунов до хэй ча»).
Огромный вопрос — кому, собственно, Ян все это рассказывает? На планете вообще остался хоть кто-то, связанный с программированием, но не в курсе, что такое и зачем нужны дженерики?
Необходимость дженериков была понятна изначально.
Было несколько итераций с большими паузами.
Просто в го не хотят бездумно запиливать новые фичи, было желание сделать правильно и не наступить на грабли. Необходимость дженериков часто преувеличивают, действительно нужны они в довольно редких случаях, поэтому фокус был на другое и с ними не спешили, стараясь отполировать решение.
Кубернетис с докером прекрасно написали без дженериков, да.
Может вам и была, а многим — не была.
Я скорее не про себя, а про команду го и её мнение на этот счёт, да и про сообщество.
Дженерики нужны, но не настолько, чтобы бежать их пилить сломя голову – позиция, которая была очень давно и которой придерживалось большинство. "Мы не знаем, как их сейчас сделать правильно" и вот это вот всё.
Я как раз 5 лет на го и пишу, так что с моей точки зрения всё происходило примерно так, и данный пропозал – логичное продолжение работы над языком.
Может быть со стороны это действительно воспринимается иначе, особенно если быть предвзятым к этому языку.
О да, тот самый Kubernetes, которые фактически написали свои собственные generics, чтобы не страдать. А так конечно необходимость преувеличивают.
Отсутствие generics не помешало Kubernetes развиться до текущего состояния.
А так конечно необходимость преувеличивают.
Не все пишут проекты уровня Kubernetes, да и проблема обычно решается другим подходом к проектированию, без попыток писать на Go как на C#, Rust или Python.
Я не погружался в то, что сделали в кодовой базе Kubernetes, так что не могу ничего сказать по этому поводу, но предполагаю, что проблему можно было решить иначе. Судя по последним изменениям в k8s.io/apimachinery
, наблюдается движения к более строгой типизации.
Пятилетний опыт коммерческой разработки на Go показал, что дженерики не нужны для большинства решаемых задач (но это не значит, что с ними в некоторых местах было бы удобнее и проще). Но вам, конечно, виднее.
Иначе то, что они выкатили на-гора в конце нулевых статически типизированный язык без дженериков и с обработкой ошибок, сделанной хуже, чем в, прости господи, Си, объяснить мне кажется сложным.
И да, без них больно и неприятно. Без человеческой обработки ошибок ещё хуже, конечно.
Вот как раз с обработкой ошибок, очень субъективно, дела идут прекрасно с точки зрения языковых конструкций. Ровно до того момента, когда надо проверять, не игнорируется ли ошибка (а именно в этом корень всех претензий). Я тогда предлагал в компилятор просто повесить проверку, не игнорируется ли из любой функции возвращаемый параметр типа Error
. Да, привет утиной типизации, с которой это труднореализуемо. Да, вопрос тонны уже написанного кода при этом открыт.
Умолчания языка с точки зрения обработки ошибок абсолютно ужасны, потому что у вас ошибки по умолчанию (в стандартной библиотеке в том числе) из каких-то проверяемых хотя бы теоретически значений возвращают только строчку с текстом. А строчка с текстом зависит не то что от ОС, а от локализации, и от кучи чего ещё. У вас нет нормального способа обработать ошибку, потому что вы знаете только "… ну что-то пошло не так". Чтобы содержательно реагировать на ошибку, эту ошибку надо уметь отличать от какой-то другой ошибки.
Файл не найден — одно. К файлу нет доступа — другое. Что-то третье — ну, оно третье.
Они теперь, конечно, пытаются это как-то исправить, но да, есть тонна написанного уже кода, который никто не перепишет, и сконкатенированные четыре сообщения об ошибке с тремя двоеточиями (это когда кто-то четырежды написал if err != nil, из них три раза это обёртки своего кода, и только четвёртый по вложенности вызов содержит, собственно, проблему и её описание, а обработать это нельзя, потому тексты просто склеиваются и логируются) будут сниться вам ещё 10 лет.
Ключи го не любит, мне кажется.
Вон, даже неиспользуемые импорты и переменные им ни-ни. А в ходе разработки это болезненно — хочу я протестить небольшой кусок кода, закомментировал использование переменной или вызов внешнего пакета и
Моё субьективное после плюсов, совершенно обратное. Затащить фич, вообще не проблема — просто потом боротся с ними тяжко. Более 16 ГБ для линковки, 40 минут билда на многоядерных ксеонах и SSD-ях в CI, разные ccache у разработчиков, эпизодические работы по ускорению билдов. Вообщем я рад что некоторые вещи можно перетащить на go.
Ну и да, языковые фичи сами по себе совершенно необязательно транслируются в увеличение времени билда и затрат памяти.
Плюсы собираются по 40 минут и жрут память как не в себя не из-за фич, а из-за "особенностей процесса". Java на тот же объём фич и кода затрачивает на пару порядков меньше времени и на порядок-два меньше памяти. Потому-что при создании языка этот момент хоть чуть-чуть, но продумали. И упростили. И да, те же дженерики — почти бесплатные с точки зрения компиляции и совсем бесплатные в рантайме (впрочем в рантайме их по сути-то и нет).
А теперь докажи, что я уравнял дженерики и темплейты. Просто темплейты в джава не нужны — хватает куда более простой абстракции.
Ага, не нужны. То-то все плачут про type erasure.
В C# те же шаблоны, только специализируются они в рантайме.
Можно долго спорить над терминологией, но прям офф доки говорят что это дженерики. Но вы, конечно же, а как иначе, умнее этих недотеп из МС…
А я где-то утверждал обратное?
В C# те же шаблоны, только специализируются они в рантайме.
или
Да какая разница к чему он ближе, если по факту это шаблоны.
Проблемы с памятью?
А давайте вы прежде чем хамить, пройдёте по ссылке и почитаете, что такое генерики?
https://habr.com/ru/company/mailru/blog/462811/#comment_20491449
Я вам не хамлю. Я констатировал факт того, что вы забыли свои же недавние фразы.
Следуя оффициальной терминологии в C# именно что generics. Причем "Generic programming" != "Generics".
Прежде чем пороть чушь, пройдите по своей ссылке, и почитайте что в C# именно что generics, и что даже code generation является одним из подходов к Generic programming. И что type erasure не является необходимым атрибутом generic'ов. И вот дабы это наглядно показать, вам привели в пример C#. На что в ответ пошла чушь навроде "В C# те же шаблоны". Глупо и некрасиво.
Обобщённое программирование (generic programming) — это программирование с использование обобщений (generics). Есть два варианта реализации обобщений:
- кодогенерация на основе шаблонов (templates).
- заворачивание разных типов в единый контейнер (boxing).
Сравнивая дженерики с шаблонами вы проявляете свою безграмотность в этом вопросе. Не надо так. На этом у меня всё. Все хорошего.
Ага-ага. А процедурное программирование — это программирование с использованием процедур. А структурное программирование — это программирование с использованием структур. А функциональное — это когда программирование с использованием функций.
Короче опять чушь несете.
type erasure запили как компромисс, который позволили по минимум переделывать виртуалку, коих для джавы мнооого. А еще сделать весь старый код совместимым с новой джавой.
Наличие type erasure в обной из реализаций дженериков не означает что оно есть везде.
С одной стороны, эти языки Turing Complete, с другой стороны, вы не можете без рефлексии/кастов написать variadic templates.
хватает куда более простой абстракции.
кому-то хватает, кому-то нет
Вон сколько времени try обсуждали и таки позже насыпали таких минусов, что тему закрыли.
Тут ведь важно как это еще тот же дебаггер будет отрабатывать и прочие мелочи. ИМХО именно дебаггинг и code coverage в тестах похоронили try.
Претензии к try
всплыли моментально — странные неявные манипуляции с переменной ошибки только ленивый не заметил.
Вот эта например — My Case Against the Contracts Proposal for Go.
Я с ней не согласен, но почитать интересно.
Да там такое впечатление, что человек читал спеку наспех по диагонали.
Вас не смущает, что 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# даже близко не имеет таких проблем.
![image](https://habrastorage.org/webt/ov/vf/2l/ovvf2l0yfwgwp-e_6sfspigqwl0.png)
Будете учить новый язык? Не на гнилом же писать…
Эмс… а что этот код вообще значит? Кажется, я неправильно вас понял. Вы хотите писать ничего не значащий код и ждете, чтобы он заработал. Простите, не понимаю ваш пример
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.
Эта диверсия с <> — она везде имеет похожие последствия в неоднозначности, так что непонятно, зачем было её вообще размножать.
Ещё не забудьте бинарный сдвиг >>, который должен закрывать вложенный дженерик. В старых плюсах обязательно было пробел ставить в середине.
Простота парсера и сохранение привычного для го синтаксиса это не религиозные соображения.
Для человека, который достаточно долго пишет на го, параметризация через скобки выглядит привычнее.
Просто так делать "как в других языках" не получится.
Пусть уже сделают наконец, а то 10 лет языку, а на нём писать нормально нельзя.
Они настолько заигрались с тем, чтобы сделать трудным написание плохих программ, что сделали трудным написание хороших.
А вот что может удивить новичков в Go, так это отсутствие способа написать простую функцию Reverse, работающую со слайсами любых типов.
Можно ведь написать функцию которая работает со слайсом любого типа, что вы обманываете новичков?
Как? Через interface{} с последующим использованием Reflect? Этот способ в статье описан. Если вы про другой, то можно пример?
Нельзя. []interface{} это отдельный тип и к, допустим, []int он никак не кастится.
Если сделать func(s []interface{}) то передать в нее []int нельзя.
Если сделать func(s interface{}) то внутри нужно делать reflect или type switch для каждого поддерживаемого типа.
Можно использовать функцию CastIntArrayToInterfaceArray
, а потом, на том результате, который вернулся — использовать функцию CastInterfaceArrayToIntArray
. Я, кстати, видел такое в реальном коде на Гоу
Вот почему нельзя было интерфейсы переиспользовать вместо контрактов? По сути они об одном и том же, разница лишь в диспетчеризации.
В Rust трейты можно и как динамические интерфейсы использовать и как контракты в генерик методах. В swift протоколы, как я понимаю аналогично работают.
А тут новая сущность вводится.
— интерфейсы приходят не бесплатно
— интерфейсы могут быть источником неявных ошибок
И как бонус: в IDE в функцию, в сигнатуре которой есть интерфейс(ы), можно передать что угодно, ошибка не высветится.
пруф: play.golang.org/p/USNSdEWJTm5
Но тем не менее контракты довольно крутая штука, было бы прикольно как типы их использовать вне концепции дженериков
func dummy(q interface{}) {
// здесь детект типа, и если тип не поддерживается, то вызываем panic()
}
Ясно, что ни о каком compile-time речи не идёт. Тип будет определяться в рантайме. И подсветить неподдерживаемый тип IDE не сможет (если только кто-то, не дай бог, не додумается вводить в go аннотации в комментариях, а-ля PHP).
При этом опять же очевидно, что если код слабо покрыт тестами, то не исключено, что где-то в функцию может быть передано значение с неподдерживаемым типом. И всё, привет panic.
Дженерики по большей степени не нужныНу и как в Go написать, к примеру, map/filter/reduce?
Через анонимную функцию, внезапно
Если вам так нужны дженерики, почему не выбираете язык где они есть c#/f#/rust/typescript? Зачем вы мучаетесь с го делая тонны лишнего бойлерплейта?
Вы уже написали только что кучу бойлерплейта, ну что, сильно устали?
Дело же не в усталости, дело в вручную написанном (в худшем случае скопипащенном неизвестно откуда) коде. Больше ручного кода — больше шансов на механическую ошибку. При этом нельзя перегибать палку, как в скриптовых языках, толерантных к приведениям типов — теряется контроль за данными в рантайме.
У меня нет функций (может я не правильно пишу) которые должны работать с разными типами, неужели сигнатура и содержимое двух фильтрующих функций для разных обьектов будут совпадать?
Да, можно вспомнить старую забавную гифку где пишут функцию для работы с интом которая превращается в 8 из-за разных типов, но это частный случай, в большинстве случаев нет такого кода, практически всегда вы знаете точно с каким кодом работаете и можете их явно указать. Я за все время работы с го (а это не один месяц) только один раз столкнулся с функцией которая должна была работать аж с 2 типами, ибо функционал был похож.
Если вам так нужны дженерики, почему не выбираете язык где они есть c#/f#/rust/typescript? Зачем вы мучаетесь с го делая тонны лишнего бойлерплейта?
потому что это требует рынок, не потому что мы этого хотим (на F# или Rust нету проектов, работы как на Go)
Через анонимную функцию, внезапноДа? Расскажите мне, пожалуйста, как это сделать. Только так, чтобы:
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#
![](https://habrastorage.org/webt/xq/sp/oj/xqspojlz_g5yiq_cpnbc_6yhvyu.png)
в го вот этот кусок it => it.a == 2 уйдет в реализацию интерфейса для типа с которым работаешь. Интерфейс с одним методом который фильтрует по полю.
Я ща прям предвижу ответ «но мне же надо 100500 разных типов так фильтровать» или «но мне же надо 100500 разных фильтров». И сразу отвечу, нет, это вы ща синтетику пытаетесь пропихнуть чтоб показать как в го это сильно сложней делается. И я не говорю что в го это супер элементарно и просто. Другое дело что в реальном коде я такого почему-то не встречал, странно правда? Или у вас реально приходят 100500 разных данных в фильтр и вы используете 100500 разных фильтрах на этих данных? Я даже на тс до такого не дохожу.
Не надо делать из го шарпы, пишите на шарпе. в большинстве случаев вы такой код пишете только на хабре чтоб что-то кому-то доказать.
И в итоге нет, 20 символов не проблема, и вам не нужно «для всех» типов писать эти 20 символов, не надо глобализацию устраивать, вы прекрасно знаете что «для всех» ничего не нужно делать.
Но дело взяло неожиданный поворот.
Беру свои слова, ибо я в очередной раз вспомнил про одну особенность в го, которую я успешно про*бал когда с вами общался.
В идеале все должно было выглядеть так: play.golang.org/p/yG5SP2NaHXR но дело в том что го работает с массивами интерфейсов через одно место, и в итоге то что я говорил не будет работать. :)
Может я со своей колокольни по незнанию так сопротивляюсь, а в других местах они часто используются.
Ну вот, к примеру, 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
;)
Мое «в идеале» выглядит так же как и бесполезная функция «ради дженериков», но ваш код хоть работать будет в отличии от моего )
Ну это если функция фильтрует слайсы, то она
"бесполезная". Ну гляньте на примеры из статьи https://blog.golang.org/pipelines, например функцию merge
. Как же весло, когда нельзя такую функцию добавить в непонятный пакет, а нужно копипастить по большому проекту!
Мне кажется ваша логика сломалась: с дженериками будет копипаста из-за того, что люди забудут об существующей реализации — это плохо, но без дженериков будет еще больше копипасты — это типо хорошо.
это никого не волнует
Это не волнует фанатиков и дураков. Так то народ постоянно стонет и ругается на обработку ошибок в Го (точнее на ее отсутствие). Блин, этож самая популярная тема похейтить сей яызк :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% я точно знаю с чем работаю, либо у меня интерфейс который делает только то что умеет и больше мне от него ничего не нужно.
зы: нет, не махонькие, к слову.
го работает с массивами интерфейсов через одно место
С массивами или слайсами?
Вот, кстати, пример из стандартной библиотеки:
type ValueConverter interface {
// ConvertValue converts a value to a driver Value.
ConvertValue(v interface{}) (Value, error)
}
Тоже с интерфейсом в качестве параметра.
В го в большинстве случаев вы используете типы явно, если вам нужно что-то универсальное, вы можете написать функцию с интерфейсом для универсальной работы (не пустым интерфейсом а нормальным), и затем после работы этой фцнкции привести свои данные назад в нужный тип.
Вы ведь точно знаете с чем работаете, или нет?
Или вы из тех людей которые используют дженерики где можно и где нельзя просто потому что «я могу»?
Вы ведь точно знаете с чем работаете, или нет?
Вы писали что-то сложнее простой утилитки когда-нибудь?
Не знать конечный тип даже в более строго типизированных языках — сплошь и рядом. От откровенной херни спасают только контракты (дженерики, интерфейсы). В Go в данный момент это карнавал непредсказуемости.
Внезапно в приложении сложней простой утилитки нужно отвязываться от типов и полагаться на интерфейсы, тогда и большинство проблем исчезнет само собой.
Вы противопоставляете динамическую диспетчеризацию статической, в то время как они должны сосуществовать. Люди в энтерпрайз сегменте уже досыта наелись ошибками в проде, которых можно было бы избежать, переложив на подгонку типов при компиляции то, что делалось в рантайме. Как пример, боль при переходе с Java 1.4 на 1.5 была невыносимой, но даже крупный и ленивый энтерпрайз рано или поздно в это ввязывался.
Я прекрасно понимаю что от типов отказаться полностью нельзя и в большинстве случаев (я выше писал) вы работаете с типами которые 100% вы знаете. Но в остальных случаях интерфейсы (нормальные а не пустые) заменяют дженерики практически полностью. В моем случае я лишь единожды столкнулся с ситуацией где мне нужны были дженерики, это очень маленький процент всего кода и в математике такие величины игнорируются, в тот раз мне было плохо, но я пережил.
Только вот есть ли у вас хоть одна функция которая должна принимать на вход «абсолютно любой тип»
В том-то и дело, что я НЕ хочу принимать абсолютно любой тип. Но в отсутствие дженериков можно сделать только так, а затем в рантайме проверить, что прислали.
И, кстати, в статье приводится отличный пример на счет 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 для своего типа?
Ну грубо говоря и контракт и интерфейс требуют от типа некоторых свойств, читай наличия методов, разница, как я понимаю, лишь в том, что интерфейс неявно реализуется для типов (утиная типизация и все дела), а в контракте ты пишешь для каких типов он поддерживается.
Неверно, имхо, поняли. Интерфейс — набор методов, которые должны быть определены на типе, чтобы этот тип можно было использовать в определенных операциях в рантайме. Т.е. к интерфейсу можно привести.
Контракт — набор ограничений, проверок в компайл-тайме, гарантирующих, что оно «соберется» (с очевидной гарантией, что будет работать, т.к. ничего «приводиться» в рантайме не будет).
Т.е. интерфейс — это тип, со всеми вытекающими. Контракт — набор ограничений.
Но никто не мешает ввести и для контрактов такую же утиную типизацию, да и в целом может лучше просто отказаться от утиной типизации в интерфейсах и реализовывать их явно? Или наоборот, оставить интерфейсы, но пометить их как 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. Зачем вы хотите, чтобы они внешне выглядели одинаково, я не совсем понимаю.
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 трейты можно и как динамические интерфейсы использовать и как контракты в генерик методах.
И это была настолько неудачная идея, что от нее спасались как могли.
В RFC есть отдельный раздел "мотивация" с причинами введения аж целого резервированного слова для решения проблемы разделения типажей на статические и динамические контракты. Если мало — есть обсуждения.
То, что человек не понимает, что он делает, и является основной проблемой — неразличимость статических и динамических контрактов, вследствие чего возникает закономерное желание их повзаимозаменять. Основную проблему введение ключевого слова не решило, но ограничило область такой взаимозаменяемости, попутно заставляя разработчика лезть в документацию. Как приятная побочка, можно теперь подсказывать компилятору о новых местах, потенциально пригодных для оптимизации обоих видов полиморфизма.
То, что человек не понимает, что он делает, и является основной проблемойИменно, и смею предположить что виноват здесь не язык, а недостаток понимания.
Из приведенного вами вопроса на stackoverflow. Человек хочет хранить поле типа MyTrait в своей структуре. Вроде бы понятная потребность, вот только как это должно работать? Тип структуры должен быть параметризирован типом и во время компиляции она должна мономорфизироваться для любого типа T реализующего MyTrait? Или на самом деле мы хотим в структуре хранить виртуальную таблицу методов этого типажа? Проблема именно в том что человек не до конца понимает что он в действительности хочет.
разделение на static и dynamic dispatch никак не изменилось
Я именно это и сказал — главная проблема не решена. Непонимание ее — просто отправная точка для хоть каких-то попыток ее решить или смягчить. Потому в итоге расплодилось синтаксиса на Trait
(легаси), dyn Trait
(чисто динамика), impl Trait
(чисто статика). Попробую поискать обсуждение планов на типажи, поделюсь если найду (сходу не находится) — вроде как там проскакивала депрекация общего типажа.
Bare синтаксис существовал когда impl Trait еще не реализовали, а значит и не было с чем его путать. Когда это сделали, решили провести унификацию: impl Trait vs dyn Trait. Но старый синтаксис пока оставили, чтобы не нарушать обратную совместимость.
Вот да, когда вводили эдишены, было довольно долгое время непонятно, зачем с ними морочиться. А потом, когда начали вылезать вот такие архитектурные ошибки, идея эдишенов как способов мягко их отсечь оказалась весьма здравой.
github.com/golang/proposal/blob/master/design/go2draft-contracts.md#why-not-use-interfaces-instead-of-contracts
Вообщем там список из семи часто спрашиваемых отклоненных идей «why not»
Сейчас более 2х лет свыше 90% написанного кода мной — на Go.
Последний год все чаще приходиться работать с чужим кодом, и все чаще меня бросает в ужас/ступор/холодный пот…
Изобилие интерфейсов, попытки скрестить паттерны, натянуть ооп и тд. и т.п., разбавля все это дело рефлексией, множеством абстракция, тучой готовых либ и решей (из которых малая часть нужна конкретной задачи проекта).
Как можно настолько похерить идею лаконичности и выразительности?
А вы говорите дженертков не хватает…
В го приходят многие из интерпритируемых языков, и многим из многих вообще мало известно о природе типов…
А потом нам с ихним кодом работать.
Простите, накипело…
Отнедавна участвую в проекте, пред.разрабы форкнулись от опенсорс, накрутили поверху своих ''фич'', другой разраб после них начал добавлять новые ''фичи'' и заодно учить Go… и это подобие с кучей сторонних либ, миллионом интерфейсов, странными реализациями сервисовнутртлибной имплементацией (map[string] func(interface{} )interface{} ) и т.п… сейчас стонет у меня на столе…
Пожалуйста, не нужно дженертков…
Пригорело, простите…
И еще касательно плохого кода. Я около года всего пишу на Go, но за это время о каких-то конкретных правилах и best-practice я слышал очень поверхностно. Я не видел, чтобы кто-то использовал какой-то фреймворк, который располагает к определенному стилю написания кода. Максимум, что я встречал в проектах — это куча линтеров. А касательно принципов — кучку высказываний аля «Возвращайте структуры, принимайте интерфейсы» по поводу которых написана тонна статей с обоснованием, но в итоге потом оказывается, что это правило не всегда актуально и не работает в куче случаев. Поэтому нет ничего странного, что многие, приходя в язык, стараются натянуть сову на глобус, не у всех хватает смелости признать, что его прошлый опыт не релевантен и нужно писать по-другому, но интересно, что при этом у новичков часто нет какого-то относительно «правильного» примера.
И, кстати, в данном случае вернемся обратно к дженерикам, люди потому и мутят кучу абстракций с interface{} и рефлексией, потому что у них нет удобного способа выразить то, что им нужно в данный момент. Язык как будто запирает тебя в рамках строгой типизации, но при этом не предлагает ничего взамен для более абстрактных вещей кроме пресловутых interface{} и рефлексии.
И да, если вы знаете какой-то свод правил или серию статей о «правильном» коде на Go, я буду признателен, если вы мне их посоветуете.
Вот интересно, кстати. Многие говорят про го-вэй и про то, что не нужно тянуть подходы из других языков.
Это понятно, но можете привести какие-то примеры хорошего го-вейного продуктивного кода (не стандартные либы), который прям вот 100% отражает принципы го?
Сам уже не первый год пишу на го, но как дело доходит до серьезной логики, все равно получаются классы, фабрики, DI и интерфейсы (ну хоть без наследования).
Потом оказалось, что понятие о говнокоде у гоферов и всех остальных кардинально отличаются. Вот код главного контрибьютора языка Go.
Нарушены буквально все принципы того же Clean Code.
И постоянно как 6-ти классник над «многочленом» хихикаю над:
Пусть у нас естьслайсспайс целых чисел.
Ни чего не могу с собой поделать.
А что вас заставляет писать на Go? Вроде же вполне естественно с накоплением опыта желать выражать свои мысли на более выразительном языке, а не на менее выразительном…
Мне до сих пор иногда кажется, что кто-нибудь из Google выскочит с большим плакатом РОЗЫГРЫШ.
Для любопытства вопросов нет, я тоже Go ради любопытства изучил и даже 1 сервис на нём написал. Но постоянно писать на таком урезанном и во многом непоследовательном языке — хз… Зачем лишать себя удовольствия от программирования?
Если только ради сильного интереса к конкретной сфере, где он получил распространение. Этот аргумент действительно выглядит убедительно :-)
программирование с дженериками позволяет представлять функции и структуры данных в виде дженериков, исключая типы.
Нельзя объяснять термин используя тот же самый термин
Программирование с дженериками позволяет акцентировать внимание на алгоритме, абстрагируясь от конкретного типа.
— одни перешли в go из типизированных языков(где есть дженерики). Они считают, что дженерики нужны.
— вторые пришли в go из языков без строгой типизации(где дженериков никогда и не было). Они считают, что дженерики не нужны.
Fight!
func Reverse(s []interface{}) {
first := 0
last := len(s) - 1
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
}
}
Или это не так?)
Для реверса, вполне можно написать такую функцию:Что, если нам надо написать функцию, которая возвращает новый массив, а не изменяет старый — как вы это сделаете?
Или это не так?)
Если реализовывать два подохда одновременно. Будет каша и путаница.
А «гибкие интерфейсы» потом вам светят несуразными паниками в рантайме (и хорошо, если девелоперском рантайме, а не в продакшне).
Если на этапе компилации уже будет все проверено, чтобы входные типы умели делать то, что требует интерфейс.
Проверено — на основании чего?
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
этой функции вообще не должно существовать. Да, можно воткнуть проверку типа рефлексией, но это процессорное время и лишние несколько строк кода.
Тут у вас есть возможность проверить, успешный ли каст или нет. Добавили проверку — все работает.
В каком случае может возникнуть необходимость делать касты без проверки я не могу представить.
есть возможность проверить, успешный ли каст или нет
Есть, но это в свою очередь увеличивает количество написанного кода и снижает его читаемость. К тому же теперь вам надо принимать решение в рантайме, что делать, если каст был неуспешный. Что в целом может усложнять архитектуру кода. А вот при наличии дженериков код бы стал лаконичнее, а потенциальные ошибки проверялись бы компилятором сразу.
Ну добавите вы проверку. Сделали такую проверку в рантайме, тип не тот, паники нету… Но что дальше? Напечатать в лог "Unsupported!!" и продолжаете выполнение? Т.е. в вашем понимании это "все работает"?
Да я считаю, что конверсию типов надо проверять. Да, все ошибки, которые возвращает api надо проверять. Да, если что-то может пойти не так, надо проверить, все ли хорошо. И если произошло что-то нештатное, действовать по обстоятельствам: отправлять ошибку выше по стеку, обрабатывать, пробовать снова или что-то еще.
Но это все риторика о том как обрабатывать ошибки.
Усложнение концепции языка не защитит от ошибок, а наоборот сделает код сложнее и ошибок станет больше. Несмотря на благие намерения. Стандартные библиотеки go показали, как на нем можно решать прикладные задачи и без дженериков.
Как раз шаблоны есть. Дженерики — не шаблоны, дженерики проверяются компилятором на этапе сборки конечной сущности, а не принимаются им по факту. Это также позволяет навешивать контракты на обобщенный тип-параметр — теперь сборкой занимается компилятор, у которого есть возможность совершать проверки.
Прошу прощения, не соображу, а как тогда будет выглядеть универсальная функция Sort, работающая абсолютно с любым типом, и встроенным, и кастомным?
вкратце
var s []*Person
// ...
sort.SliceFn(s, func(p1, p2 *Person) bool { return p1.Name < p2.Name })
type Gene type
и получить все плюшки Hindley–Milner не меняя синтакис, а прямо в существующем?func ReverseInts(s []int) {
first := 0
last := len(s)
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
}
}
Зачем нужны дженерики в Go?