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

Программист программ

Отправить сообщение

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


Очевидно, в такой постановке вопроса, какую ставите вы, решения не существует.

Может быть быть всё-таки не стоит перевирать слова?


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


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


Они решили бы некоторые проблемы, но отсутствие дженериков не всегда создаёт дополнительные трудности, какие были бы в других языках (в которых есть), убрав их оттуда.

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

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


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


Другой пример: опять же, простейшая реализация message bus
Pipe-фреймворки и пулы объектов

В случае с message bus и другими условными шинами для метрик, централизованных логов и т.п. вам, вероятно, либо важно получать какие-то конкретные поля (вроде количества хитов, даты, пользователя и т.п.) или динамический набор данных определенной структуры (вроде k(string)-v(string)), либо абсолютно не важно, что приходит, и нужно только тип знать, например.


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


Во втором случае не все языки с дженериками дают информацию по любому объекту. Rust, насколько я помню, имеет typeid и вычисляет на этапе компиляции его, в C++ без рантайма это сделать нельзя, в Java что-то похожее есть, про C# и др. не знаю. В Go можно использовать рефлексию в данном случае.

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


Потому что с ним код оказался понятнее, быстрее и проще.

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


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

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


те кому нужны дженерики — либо ушли с Go, либо никогда на него не переходили.

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


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

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


А я не использую дженерики в Go, но очень интенсивно использую шаблоны в C++.
При этом в Go есть композиция, а в C++ её нет в таком виде, в котором она есть в Go. Значит ли это, что на C++ нельзя решать задачи, которые можно сделать на Go используя композицию?
Копируя 1 в 1 — безусловно нельзя, но если пользоваться средствами C++ — конечная логика будет отличной от Go, но давать будет тот же результат.


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

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


Ничем не обоснованное утверждение.

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


Если хотя бы какое-то время поработать с библиотеками и проектами на Go, можно заметить, что подход к проектированию там не такой, что нужно "выполнять действия НАД объектом типа Х", а "вызывать У объекта методы реализующие интерфейс Х".


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


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

И там тоже есть где применить дженерик...

Вопрос — нужно ли?


совершенно реальную ситуацию

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


Но как часто в веб-сервисах, консольных утилитах и иных более-менее реальных приложениях на Go нужно делать обобщённые структуры данных, которые будут универсальны и применимы во многих других приложениях и библиотеках?


Всякие вещи вроде деревьев в прикладных задачах зачастую используются в K-V (и не только) хранилищах, которые создаются с определённой целью. Т.е. хранить что-то конкретное в конкретном приложении. И за пределами некой системы скорее всего будут бесполезны сами по себе.


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


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

sync.Map — это довольно рафинированый пример.
Предположим, мы делаем какой-то веб-сервис. Для чего нам использовать map с синхронизацией? Если map используется как сессионное хранилище (время жизни запроса), то скорее всего, к нему доступа за рамками этого запроса не производится и можно использовать обычный map.
В качестве глобального применения с ходу приходит в голову хранилище короткоживущих сессий, токенов и т.п. с привязкой к пользователю.


Итак, у нас есть map, который расшарен между потоками и его надо синхронизировать.
Вы берёте sync.Map, начинаете туда писать, допустим, UUID в качестве ключа и структуру в качестве значения.
Потом оказывается нужно помимо самого map иметь ещё какой-нибудь счётчик сессий незарегистрированных юзеров.
Вы выкидываете sync.Map, оборачиваете обычный map[UUID]UserStruct в структуру с RWMutex, добавляете в эту структуру поле со счётчиком и вместо методов Delete/Load с интерфейсами в качестве параметра и возвращаемого значения делаете свои обёртки, которые принимают UUID как ключ и возвращают указатели на структуру пользователя.


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


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

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


Ещё сразу бросается минус локализации: англоязычная локаль != 12-часовое отображение времени.
Может быть есть (или можно добавить) флажок для выбора 24/12, или брать это из системной локали (не знаю, может ли браузер так делать).

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

Тем не менее, вместо общих фраз о том, что дженерики — это необходимость и без них никак, хотелось бы всё таки услышать что-то более приближенное к реальному программированию.
Потому что пока получается, как и всегда: "- Дженерики нужны. — А зачем? — Ну потому что надо, универсально, без них нельзя. — Покажите, как можно использовать. — Ну вот без них нельзя и всё тут."

Не могли бы вы привести пример (не абстрактные рассуждения, которые повторяют просто определение "generics programming") задачи, которая бы часто встречалась при программировании на Go, и которую без дженериков решить было бы невозможно, или ужасно трудно?


Представьте, что в Go забыли завести тип отображения

Но его ведь не забыли ввести, верно? К чему тогда это?


Фундаментальных типов данных не бесконечное количество и при желании реализовать можно какую угодно новую структуру данных (без красивого синтаксического сахара, но можно), если чего-то очень важного нет. sync.Map, например, так и реализован.

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


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


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


И, субъективно говоря, композиция больше подходит для тех задач, для которых обычно используют Go, чем "нормальное" наследование.
Отмечу, что не лучше наследования в принципе, а просто удобнее для определённых задач. И учитывая то, как именно работает композиция в Go, можно сказать, что за исключением проблем, указанных выше, дженерики при отсутствии "нормального" наследования не особо-то и нужны.

1000 — это оригинальный zigbee, корпус от которого взял автор.
3600 — это самоделка с z-wave.


Всё сходится, просто вам видимо поспать надо, немножко невнимательны (:

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

Учитывая, что 512 Гб на сервер — это вполне стандартная (чуть выше среднего) современная конфигурация, вам нужно всего 20 серверов, чтобы хранить это. Если учесть резервирование, то 40 серверов. Для хранения самых пользовательских данных вам потребуется в сотни раз больше, так что это не проблема.

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

Часть про то, что вы ненавидите Go, была обязательной, чтобы свои впечатления выразить? То есть, никак не обойтись без того, чтобы хейтить и просто "что-нибудь про UDP" почитать? Раз от статьи не воротит настолько, что решили почитать, значит не так уж и ненавидите, наверное.


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

Это верно, однако, и RAII — это не серебряная пуля. Чтобы в этом случае дескриптор закрылся — объект должен быть освобождён, а если он висит ожидая чего-то, то это не особо поможет.


В том же Go проблемы с горутинами и ресурсами, в основном, связаны не с тем, что кто-то забывает вызвать Close (к тому же финалайзеры вызываются, когда os.File собирается GC, например. эдакий недетерминированный RAII), а из-за того, что условие выхода из горутины не определено.
Про таймауты на http-соединения забывают намного чаще. А это, в принципе, мало зависит от языка.


Можно возразить, мол, не во всех языках есть "легкие треды" и будете правы, там где приходится иметь пул потоков — он забьётся и всё колом встанет, а в Go будет расти память на висящих горутинах, пока процесс не умрет по OOM.

дедлоки каналов, по невнимательности\неопытности заруинит приложение и вообще отобьет охоту иметь дело с го

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


интерфейсы (они вообще развязывают руки для любого извращения)

Интерфейсами злоупотребляют в тех случаях, когда по каким-то причинам думают, что func f(interface{}) == template typename<T> f(T), а если так не делать (а так делать прямо не советуют в официальных ресурсах), то и проблем не будет


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

Но незакрытые дескрипторы вызовут проблемы и если вы оставите их открытыми в любом другом языке. При этом, опять же, везде где происходит работа с чем-то таким, в документации используют что-то вроде defer XXX.Close().


йота — странный модификатор констант, оно мне больше напоминает «финт для продвинутых», пользы от наличия enum больше.

Это разве страшно? Он не приводит к запутанному коду или содержит слишком дикую магию, но, конечно, выглядит как костыль.


возможность делать одно и тоже несколькими способами

Строго говоря, не одно и то же:


// область видимости err ограничена внутренностью if и не перезаписывает переменную err извне
if err := f(); err != nil {  
    // ...
}

err := f() // область видимости err ограничена функцией, и дальше не может быть переопределена
if err != nil {
    // ...
}

v1 := new(int) // так сделать можно
v2 := &int     // так сделать нельзя

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


  • В Go нет такой ситуации, как, например, в C, когда в переменной может храниться что угодно и надо принудительно проинициализировать нулём, пустой строкой и т.п.
  • Как быть с типами, которые не позволяют создавать константы?
    • Инлайновое объявление какой-нибудь структуры или слайса превратит код в лапшу (и убьёт удобство)
    • Если позволять в качестве значений по-умолчанию вставлять изменяемые переменные, то непонятно, в какой момент вычислять дефолтное значение

По-второму всё довольно очевидно потому что в Go нет наследования, а есть композиция. И если понимать, что это именно вложенный самостоятельный объект, а не классическое наследование, то получается, что прямее некуда.
Например, можно делать так:


type B struct {
    *A
    Two int
}

И в этом случае B.A по умолчанию не инициализируется и nil. И для указателя нормальное дело быть инициализированным в nil по-умолчанию, так что прямее невозможно сделать как раз потому, что это и не наследование вовсе.

С этим есть нюансы.


  • Некоторые утилиты для go generate и средства работы с кодом для IDE до сих пор в некоторых случаях работают не так хорошо, как с пакетами в GOPATH, а то и вовсе не работают. С этим постепенно справляются, но тем не менее.
  • Не слишком понятно, почему требуется "v" в начале имени тега.
  • Возможно, только у меня, но принудительный вендоринг иногда выкидывает поддиректории из репозиториев, из которых подключается несколько библиотек (например, если из github.com/a/b импортируются /c и /d, в папке vendor может быть github.com/a/b/c, то /d не будет), происходило с gogo-proto

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

Бесплатный PVS-Studio для тех, кто развивает открытые проекты

  • Бесплатно? Бесплатно.
  • PVS-Studio? PVS-Studio.
  • Для тех, кто развивает открытые проекты? Тоже верно.

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


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

Тогда нечего затевать «акт щедрости».

"Либо мне, либо никому"? Собственно, нигде и не шло речи о бескорыстной раздаче лицензий. Вы же не забываете, что это не некоммерческая организация? И любая деятельность коммерческой компании ведёт в конечном счёте к увеличению прибыли.
Вот же капиталистические редиски, правда? Мало того, что бесплатно продукты раздают, так ещё и не той целевой аудитории, которая создаст достаточный объем саморекламы посредством использования продукта.


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


Он, получается, тоже не получит возможности данного «щедрого» вознаграждения.

У kernel.org между прочим есть Linux Foundation, который много денег вливает в разработку ядра. О таких ситуациях в статье описано.

Информация

В рейтинге
1 811-й
Откуда
Россия
Зарегистрирован
Активность