Я ничего не слышал про планы на variadic generics. В плане языковых фич в данный момент есть планы на const generics, GATs (generic associated types) и specialization.
Но, даже если бы variadic generics были, я сомневаюсь, чтобы они помогли в данной ситуации, ведь они оперируют на уровне сигнатуры типов, а не сигнатуры аргументов функции. Вещи, конечно же, связанные, но далеко не одно и то же.
А в чём проблема использования макросов? ИМХО, одна из положительных сторон Rust — это как раз first-class citizen макросы. Они очень гибки и очень помогают для решения часто самых неожиданных задач, гораздо более чаще, нежели затыкают "дыры" выразительности языковых конструкций. После Rust их часто очень не хватает в других языках.
В Rust нет variadic function arguments, потому существует несколько вариантов join'а в зависимости от кол-ва аргументов: join, join3, join4,… join_all.
Как вы догадываетесь, это обилие было бы не слишком приятно видеть в коде, потому эти функции сверху прикрыты макросом join!, который при разворачивании автоматически использует необходимую функцию.
Собственно, использование макроса для того, чтобы предоставить возможности variadic arguments — достаточно стандартный приём в Rust и применяется много где.
Сам async/.await тут ни при чём. Просто в Rust и без async/.await модель исполнения futures сделана poll-based, а не push-based как к этому все привыкли в других языка.
Это был осознанный выбор и этому есть ряд причин, которые неплохо описаны в этим статьях:
Что же касается "невозможностей", то таких нет. Всё возможно, просто выполняется немного иначе, нежели привыкли, и с оглядкой на poll-based ленивость футур. Конкретно ваш пример решается следующим образом:
let (a, b) = futures::join!(get_a_async(), get_b_async()).await;
Все же EventStore и EventBus немного разные штуки. В статье Kafka используется именно в качестве EventBus. Есть ли там EventStore — вообще умалчивается.
Для агрегации (схлопывания, дегидрации) событий в конечную версию модели Kafka действительно не очень подходит, ибо нет возможности выбрать события только для заданного идентификатора. То есть, либо выбирать все-все события и фильтровать подряд руками, что далеко не производительно. Либо под каждый идентификатор (или группу) — отдельный топик, но Kafka плохо дружит с зашкаливающем кол-вом топиков, ведь все-таки Kafka — это последовательный лог.
В статье же, Kafka не используется в данном ключе, а используется только как надежный доставщик событий. Где потом эти события хранятся, хранятся ли вообще, и в каком виде (чистом или агрегированном) — уже вопрос каждого микросервиса отдельно.
Ну вот в brainfuck, к примеру, программисты как раз и пишут "код", а не функции =)
Я понимаю вашу позицию, я и сам её сторонник. Но разработчики Go спроектировали язык именно так под свои задачи. Им надо именно такой трейд-офф. Нам лишь остаётся либо пользоваться этим инструментом, либо выбрать другой. Каждый сам решает.
Сильная/строгая (strong) и слабая (weak) типизации — это про явное и неявное приведение типов. У Go строгая типизация, так как приведение типов всегда явное.
Касательно информации от разработчика Go, то подозреваю, что имеется в виду этот известный комментарий от Ian Taylor. Увы, но в нём речь идёт не о типизации (typing), но о системе типов (type system). Понятия хоть и связанные, но все же разные. Слабая система типов Go выражается в том, что шаг вправо/влево, и уже приходится чертыхаться с interface{}, теряя гарантии проверки корректности типов на этапе компиляции (ошибка несоответствия типа выстрелит только во время выполнения). И Ian Taylor сообщает, что это как раз сделано намеренно, дабы программисты "писали код, а не типы".
Увы, но просто понимания недостаточно. Даже наличия реального практического опыта недостаточно. Можно понимать, постоянно созерцать и видеть работу ума/эго как в себе, так и в других, и даже регулярно практиковать практики, но при этом не двигаться. Всё это будет оставаться увлечением, и человек будет от этого то уходить, когда поднадоест, то снова возвращаться под наплывом вдохновения или случая, либо же вообще делать как некую регулярную проформу. Даже если ощущал и ощущал то самое несравнимое со всем земным — этого всё ещё недостаточно.
Чтобы этим постоянно жить, как отписываете Вы, чтобы двигаться и чтобы прийти, необходим внутренний выбор и, как следствие, готовность идти до конца не смотря ни на что. Чтобы совершить подобный внутренний выбор необходима достаточная мотивация. Легче всего тем, кто этого жаждет. Не увлекается, не интересуется, не пробует, не изучает, не считает что надо, а именно жаждет как воздуха для жизни.
А судить человека за внутренний выбор — это навязывание своего выбора ему. Ведь право совершать внутренний выбор на своё усмотрение есть у каждого. Каждый сам решает как ему жить, и сам потом переживает последствия своих решений.
Long story short: человек всего лишь живет так, как хочет либо считает правильным. Его не интересует просветление, либо интересует недостаточно, либо что-то другое интересует больше. Это его право. Не требуйте от него быть кем-то там, живущим так-то =)
Не всё так просто. Rust ведь тоже не дураки дизайнят, и там много людей которые тоже хотели бы видеть HKT в составе. Увы, на практике, до сих пор толком до конца никто не представляет как их красиво вписать и реализовать в Rust. Этот вопрос считается открытым и требующим глубокого исследования. Кратко со списком проблем можно ознакомиться в этом треде от withoutboats (core team).
Ждать пока этот исследовательский момент как-нибудь разрешится сообщество тоже не готово. Все хотят уже брать и писать крутые штуки на Rust. Без вменяемой асинхронности этого не получится. Соответственно, в данный момент для async/await не придумано лучших альтернатив в Rust. Это прагматичный выбор. Тем не менее он не отменяет монадок/HKT в будущем, если таки придумают как туда их красиво вписать.
Если же у Вас есть хорошие идеи/предложения по реализации HKT в Rust — добро пожаловать на официальный форум и в RFC-процесс. Выскажите свои идеи. Сообщество будет Вам очень благодарно, на самом деле.
Мне лично с GoConvey понравилось работать больше всего. И дело далеко не в синтаксисе. Да, синтаксис, не такой приятный как у того же gingko, и привычные BDD идиомы given/before/after и assert'ы вывернуты немного иначе, нежели привыкаешь в других языках (в этом плане gingko гораздо более похож на стандартные реализации). Но к синтаксису, на самом деле, просто привыкаешь со временем и достаточно быстро.
Основная киллер-фича GoConvey — это модель исполнения его каскадов, и она невероятно удобна для жирнющих тестов, где Вам нужно инициализировать кучу моков/стабов а потом прогонять 100500 сценариев с ними. ginkgo же в этом плане заставляет инициализацию выносить в before-блоки, а так как эти блоки являются просто отдельными Go'шными функциями, то сразу рисуется проблема передачи наинициализированного дела в describe- и after-блоки, хоть через внешние переменные, хоть как. В GoConvey же, ты просто об этом даже не думаешь, всё просто работает без всяких чертыханий, ибо определения из внешних каскадов автоматически прокидываются во внутренние по замыканию, а параллельные сценарии выполнения автоматически разделяются на отдельные контексты. Это очень сильно уменьшает шум и бойлерплейт, оставляя в коде теста для чтения строго то, что нужно. Написание таких тестов тоже становится очень приятным делом.
Ну а если соединить GoConvey и httpexpect, то это прямо благодать какая-то для написания E2E-тестов для всяких API'шек.
Чтобы ещё "зашакалить" вес результирующего WASM бинарника, используют/-вали wasm-gc, wasm-snip, wasm-opt из binaryen. Детальнее есть в этой статье.
Общее желание и тенденция к уменьшению WASM-бинарников наблюдается.
К тому же, WASM by design парсится прямо во время скачивания, соответственно у него нет фазы парсинга и компиляции после загрузки, как у JS.
Ну черт его знает… Прогнал все бенчи. Примерно 50/50. Там где JS уделывает — как правило, разница не большая. А вот у WASM в некоторых бенчах есть прям x2/x3 прирост.
Не могу сказать, что реальность прям так уж и расходится с моими текущими ожиданиями. Но, видимо, у нас с вами просто разные ожидания.
смотрю на рожденные языки в диапазоне последных 10-ти лет, и ниодин не нашел, который бы не позволял создавать программы с неправильными адресациями, некорректными указателями и т.п…
Все это хорошо и красиво звучит, пока не сталкиваешься с проблемой, которая идиоматикой не решается нормально, потому что идиоматика просто не предусмотрела её решения.
И ладно бы это был бы какой-то rocket science, так нет же — обычная рутина, решенная уже не раз в других языках. Ты при этом как бы и не против идиоматики — спрашиваешь у опытных, и в итоге получаешь: "это не идиоматично, это тебе не нужно" (с)… ну зашибись теперь… а проблема типа "сама как-нибудь чудом там решится… может быть… ну или пропадёт… может быть".
Следующий этап — пытаешься решить сам (велосипед, ага) идиоматичными инструментами. И так, и эдак, крутишься, вертишься, но постоянно при этом теряешь другие плюшки языка — то здравствуй interface{}, и пока type safety, то горы бойлерплейта писанного руками и потому абсолютно неподдерживаемого.
Дальше до тебя начинает доходить осознание, что во всех других языках эта проблема решается именно так, потому что есть некие фундаментальные требования/ограничения, решения которых идиоматикой Go просто тупо не предусмотрено. И инерция мышления тут вообще ни причем, сколько не бейся, и сколько не разгоняй своё мышление. И начинается финальный акт, когда ты "замазываешь" дыры в дизайне Go банальной кодогенерацией, для которой, между прочим, тоже не предусмотрено никакого стандартного тулчейна/подхода/инструмента, и каждый кодогенерирует как умеет.
В результате, ты часто не пишешь код, но пишешь код, который генерирует код, причем на каких-то костылях. Что никак не user friendly, но вменяемой альтернативы — просто нет.
Чтобы не быть голословным — вот задачка: сделайте реализацию канала в Go, который при полном заполнении буфера не блокирует последующую попытку записи, ожидая чтения (стандартное поведение буферизированного канала в Go), но выкидывает самое старое значение и принимает новое, не блокируя пишущего в канал. А после того как реализуете и отладите (особенно доставят проблемы с утечкой ресурсов при окончании работы с каналом), сделайте так, чтобы эту реализацию можно было использовать для произвольного типа, желательно включая базовые, без необходимости ваять всякие обертки (бойлерплейт, ага), при этом сохраняя type safety (проверку типов на этапе компиляции).
Аргументы "type safety не нужно, interface{} рулит" и "вам подобные каналы не нужны"(с) не принимаются, ибо, во-первых, использовать interface{} вроде как не идиоматично, и вполне себе дорого (тайпкасты недешевые), да и гарантии на стадии компиляции никогда не лишние, а, во-вторых, подобные каналы нужны на практике.
Go имеет много плюсов, и во многом хорош, но и недостатки, как и конкретные проблемы, у этого языка есть. Отрицать их наличие, ссылаясь на идиоматику и инерцию мышления, — это тупо закрывать глаза на проблемы. А запинывать решения грязными сапогами в Go приходится потому, что он, как оказывается достаточно часто, просто не предлагает никакого решения взамен.
Как бы да, но для внедрения зависимостей, как бы, и нет.
Все дело в том, что в Go внедрение зависимостей происходит на этапе инициализации приложения. То есть, с начала «всё подняли», а потом «бежим в рантайм». И если у Вас есть хотя бы 1 E2E-тест, то проблемы с паниками в механизме внедрения полностью пресекаются. И даже более того, поощряются, потому что это нормально хлопнуть приложение полностью на этапе инициализации, а бойлерплейт это уменьшает невообразимо.
Есть стероиды в виде Masterminds/sprig. С ними Go'шный шаблонизатор более чем юзабелен. Подобная связка без проблем используется в Kubernetes Helm и в Hugo, и, по мощности, в принципе, не уступает тому же PHP как шаблонизатору. Последний, кстати, как шаблонизатор тоже так себе. Лучше уж что-то вменяемое типа jinja2/twig/liquid с наследованием шаблонов и прочими вкусняшками)
Целая горсть таких. К счастью, composer require dshafik/php7-mysql-shim «works like a charm». Больше проблем доставили другие несовместимости между 5 и 7. Но, в целом, даже дремучее legacy за один рабочий день вполне себе втаскивается на 7.2.
Я ничего не слышал про планы на variadic generics. В плане языковых фич в данный момент есть планы на const generics, GATs (generic associated types) и specialization.
Но, даже если бы variadic generics были, я сомневаюсь, чтобы они помогли в данной ситуации, ведь они оперируют на уровне сигнатуры типов, а не сигнатуры аргументов функции. Вещи, конечно же, связанные, но далеко не одно и то же.
А в чём проблема использования макросов? ИМХО, одна из положительных сторон Rust — это как раз first-class citizen макросы. Они очень гибки и очень помогают для решения часто самых неожиданных задач, гораздо более чаще, нежели затыкают "дыры" выразительности языковых конструкций. После Rust их часто очень не хватает в других языках.
В Rust нет variadic function arguments, потому существует несколько вариантов join'а в зависимости от кол-ва аргументов:
join
,join3
,join4
,…join_all
.Как вы догадываетесь, это обилие было бы не слишком приятно видеть в коде, потому эти функции сверху прикрыты макросом
join!
, который при разворачивании автоматически использует необходимую функцию.Собственно, использование макроса для того, чтобы предоставить возможности variadic arguments — достаточно стандартный приём в Rust и применяется много где.
Сам
async
/.await
тут ни при чём. Просто в Rust и безasync
/.await
модель исполнения futures сделана poll-based, а не push-based как к этому все привыкли в других языка.Это был осознанный выбор и этому есть ряд причин, которые неплохо описаны в этим статьях:
Что же касается "невозможностей", то таких нет. Всё возможно, просто выполняется немного иначе, нежели привыкли, и с оглядкой на poll-based ленивость футур. Конкретно ваш пример решается следующим образом:
Для агрегации (схлопывания, дегидрации) событий в конечную версию модели Kafka действительно не очень подходит, ибо нет возможности выбрать события только для заданного идентификатора. То есть, либо выбирать все-все события и фильтровать подряд руками, что далеко не производительно. Либо под каждый идентификатор (или группу) — отдельный топик, но Kafka плохо дружит с зашкаливающем кол-вом топиков, ведь все-таки Kafka — это последовательный лог.
В статье же, Kafka не используется в данном ключе, а используется только как надежный доставщик событий. Где потом эти события хранятся, хранятся ли вообще, и в каком виде (чистом или агрегированном) — уже вопрос каждого микросервиса отдельно.
Ну вот в brainfuck, к примеру, программисты как раз и пишут "код", а не функции =)
Я понимаю вашу позицию, я и сам её сторонник. Но разработчики Go спроектировали язык именно так под свои задачи. Им надо именно такой трейд-офф. Нам лишь остаётся либо пользоваться этим инструментом, либо выбрать другой. Каждый сам решает.
Сильная/строгая (strong) и слабая (weak) типизации — это про явное и неявное приведение типов. У Go строгая типизация, так как приведение типов всегда явное.
Касательно информации от разработчика Go, то подозреваю, что имеется в виду этот известный комментарий от Ian Taylor. Увы, но в нём речь идёт не о типизации (typing), но о системе типов (type system). Понятия хоть и связанные, но все же разные. Слабая система типов Go выражается в том, что шаг вправо/влево, и уже приходится чертыхаться с
interface{}
, теряя гарантии проверки корректности типов на этапе компиляции (ошибка несоответствия типа выстрелит только во время выполнения). И Ian Taylor сообщает, что это как раз сделано намеренно, дабы программисты "писали код, а не типы".Увы, но просто понимания недостаточно. Даже наличия реального практического опыта недостаточно. Можно понимать, постоянно созерцать и видеть работу ума/эго как в себе, так и в других, и даже регулярно практиковать практики, но при этом не двигаться. Всё это будет оставаться увлечением, и человек будет от этого то уходить, когда поднадоест, то снова возвращаться под наплывом вдохновения или случая, либо же вообще делать как некую регулярную проформу. Даже если ощущал и ощущал то самое несравнимое со всем земным — этого всё ещё недостаточно.
Чтобы этим постоянно жить, как отписываете Вы, чтобы двигаться и чтобы прийти, необходим внутренний выбор и, как следствие, готовность идти до конца не смотря ни на что. Чтобы совершить подобный внутренний выбор необходима достаточная мотивация. Легче всего тем, кто этого жаждет. Не увлекается, не интересуется, не пробует, не изучает, не считает что надо, а именно жаждет как воздуха для жизни.
А судить человека за внутренний выбор — это навязывание своего выбора ему. Ведь право совершать внутренний выбор на своё усмотрение есть у каждого. Каждый сам решает как ему жить, и сам потом переживает последствия своих решений.
Long story short: человек всего лишь живет так, как хочет либо считает правильным. Его не интересует просветление, либо интересует недостаточно, либо что-то другое интересует больше. Это его право. Не требуйте от него быть кем-то там, живущим так-то =)
Не всё так просто. Rust ведь тоже не дураки дизайнят, и там много людей которые тоже хотели бы видеть HKT в составе. Увы, на практике, до сих пор толком до конца никто не представляет как их красиво вписать и реализовать в Rust. Этот вопрос считается открытым и требующим глубокого исследования. Кратко со списком проблем можно ознакомиться в этом треде от withoutboats (core team).
Ждать пока этот исследовательский момент как-нибудь разрешится сообщество тоже не готово. Все хотят уже брать и писать крутые штуки на Rust. Без вменяемой асинхронности этого не получится. Соответственно, в данный момент для async/await не придумано лучших альтернатив в Rust. Это прагматичный выбор. Тем не менее он не отменяет монадок/HKT в будущем, если таки придумают как туда их красиво вписать.
Если же у Вас есть хорошие идеи/предложения по реализации HKT в Rust — добро пожаловать на официальный форум и в RFC-процесс. Выскажите свои идеи. Сообщество будет Вам очень благодарно, на самом деле.
Мне лично с GoConvey понравилось работать больше всего. И дело далеко не в синтаксисе. Да, синтаксис, не такой приятный как у того же gingko, и привычные BDD идиомы given/before/after и assert'ы вывернуты немного иначе, нежели привыкаешь в других языках (в этом плане gingko гораздо более похож на стандартные реализации). Но к синтаксису, на самом деле, просто привыкаешь со временем и достаточно быстро.
Основная киллер-фича GoConvey — это модель исполнения его каскадов, и она невероятно удобна для жирнющих тестов, где Вам нужно инициализировать кучу моков/стабов а потом прогонять 100500 сценариев с ними. ginkgo же в этом плане заставляет инициализацию выносить в before-блоки, а так как эти блоки являются просто отдельными Go'шными функциями, то сразу рисуется проблема передачи наинициализированного дела в describe- и after-блоки, хоть через внешние переменные, хоть как. В GoConvey же, ты просто об этом даже не думаешь, всё просто работает без всяких чертыханий, ибо определения из внешних каскадов автоматически прокидываются во внутренние по замыканию, а параллельные сценарии выполнения автоматически разделяются на отдельные контексты. Это очень сильно уменьшает шум и бойлерплейт, оставляя в коде теста для чтения строго то, что нужно. Написание таких тестов тоже становится очень приятным делом.
Ну а если соединить GoConvey и httpexpect, то это прямо благодать какая-то для написания E2E-тестов для всяких API'шек.
Чтобы ещё "зашакалить" вес результирующего WASM бинарника, используют/-вали
wasm-gc
,wasm-snip
,wasm-opt
из binaryen. Детальнее есть в этой статье.Общее желание и тенденция к уменьшению WASM-бинарников наблюдается.
К тому же, WASM by design парсится прямо во время скачивания, соответственно у него нет фазы парсинга и компиляции после загрузки, как у JS.
Не могу сказать, что реальность прям так уж и расходится с моими текущими ожиданиями. Но, видимо, у нас с вами просто разные ожидания.
Из свежего, например, вот годная статья. Из неё же по результатам:
Эммм… Rust?
Все это хорошо и красиво звучит, пока не сталкиваешься с проблемой, которая идиоматикой не решается нормально, потому что идиоматика просто не предусмотрела её решения.
И ладно бы это был бы какой-то rocket science, так нет же — обычная рутина, решенная уже не раз в других языках. Ты при этом как бы и не против идиоматики — спрашиваешь у опытных, и в итоге получаешь: "это не идиоматично, это тебе не нужно" (с)… ну зашибись теперь… а проблема типа "сама как-нибудь чудом там решится… может быть… ну или пропадёт… может быть".
Следующий этап — пытаешься решить сам (велосипед, ага) идиоматичными инструментами. И так, и эдак, крутишься, вертишься, но постоянно при этом теряешь другие плюшки языка — то здравствуй
interface{}
, и пока type safety, то горы бойлерплейта писанного руками и потому абсолютно неподдерживаемого.Дальше до тебя начинает доходить осознание, что во всех других языках эта проблема решается именно так, потому что есть некие фундаментальные требования/ограничения, решения которых идиоматикой Go просто тупо не предусмотрено. И инерция мышления тут вообще ни причем, сколько не бейся, и сколько не разгоняй своё мышление. И начинается финальный акт, когда ты "замазываешь" дыры в дизайне Go банальной кодогенерацией, для которой, между прочим, тоже не предусмотрено никакого стандартного тулчейна/подхода/инструмента, и каждый кодогенерирует как умеет.
В результате, ты часто не пишешь код, но пишешь код, который генерирует код, причем на каких-то костылях. Что никак не user friendly, но вменяемой альтернативы — просто нет.
Чтобы не быть голословным — вот задачка: сделайте реализацию канала в Go, который при полном заполнении буфера не блокирует последующую попытку записи, ожидая чтения (стандартное поведение буферизированного канала в Go), но выкидывает самое старое значение и принимает новое, не блокируя пишущего в канал. А после того как реализуете и отладите (особенно доставят проблемы с утечкой ресурсов при окончании работы с каналом), сделайте так, чтобы эту реализацию можно было использовать для произвольного типа, желательно включая базовые, без необходимости ваять всякие обертки (бойлерплейт, ага), при этом сохраняя type safety (проверку типов на этапе компиляции).
Аргументы "type safety не нужно,
interface{}
рулит" и "вам подобные каналы не нужны"(с) не принимаются, ибо, во-первых, использоватьinterface{}
вроде как не идиоматично, и вполне себе дорого (тайпкасты недешевые), да и гарантии на стадии компиляции никогда не лишние, а, во-вторых, подобные каналы нужны на практике.Go имеет много плюсов, и во многом хорош, но и недостатки, как и конкретные проблемы, у этого языка есть. Отрицать их наличие, ссылаясь на идиоматику и инерцию мышления, — это тупо закрывать глаза на проблемы. А запинывать решения грязными сапогами в Go приходится потому, что он, как оказывается достаточно часто, просто не предлагает никакого решения взамен.
Все дело в том, что в Go внедрение зависимостей происходит на этапе инициализации приложения. То есть, с начала «всё подняли», а потом «бежим в рантайм». И если у Вас есть хотя бы 1 E2E-тест, то проблемы с паниками в механизме внедрения полностью пресекаются. И даже более того, поощряются, потому что это нормально хлопнуть приложение полностью на этапе инициализации, а бойлерплейт это уменьшает невообразимо.
composer require dshafik/php7-mysql-shim
«works like a charm». Больше проблем доставили другие несовместимости между 5 и 7. Но, в целом, даже дремучее legacy за один рабочий день вполне себе втаскивается на 7.2.