Прежде всего отмечу, что, имхо, польза может быть особенно в проектах с большим, сложным внутреннем API, где важно строгое соблюдения контрактов/бизнес-логики.
Я это уже читал, но так и не понял в чём конкретно эта "польза". Другими словами, я не понимаю, каким образом дополнительная связность (хоть её наличие вы и отрицаете) может быть полезна. Особенно, в "проектах с большим, сложным внутреннем API".
Сборок может быть сколько надо, нет ограничений ...
Ну я таки глянул код. Позвольте усомниться в этом вашем утверждении. Если аттрактор и друг это типы из разных сборок, то возникает циклическая зависимость между сборками. Причина - та самая "лишняя" связность, наличие которой вы отрицаете, но которой, тем не менее, нет в канонических вариантах решения.
Собственно, это (наличие циклической зависимости) было очевидно и без кода... но я надеялся, что у вас там она разрешается, например, через генерацию отдельной сборки. Я ошибался.
В разных они сборках или нет, в любом случае, они знают друг о друге ...
При традиционных подходах к - та часть, что в вашем варианте становится аттрактором ничего не знает о "дружественной" части.
Исходная сущность (класс Me) внешне как раз не мутабельная (readonly), а "через интерфейс" предоставляется строго избирательная "проекция" с доступом к внешне скрытым или/и readonly мемберам.
Я это понял :-) Моя реплика была про то, что каноническое решение выглядит с точностью до наоборот. Если хотите, то вопрос в том, почему у вас не так? Почему за основу взят - так сказать - "вывернутый" вариант?
... а правило "три не" в плюсах, хорошее правило ...
Как я понимаю, мы о разных "трех не" говорим. Речь была о том, что "дружба" не наследуется. Не просто так. Очевидно, что - как минимум - в отношении к интерфейсам у вас "всё не так". Я, собственно, поэтому и упомянул явную реализацию.
Помимо основного тела статьи, в «Приложении» сделал много примеров задач с возможным применением, обоснованием зачем.
К сожалению, там нет "обоснования зачем". Там есть "смотри как могу". Любой из рассмотренных примеров замечательно решается без "этого". Собственно, в большинстве случаев, вы сами же об этом и пишите. Поэтому и был мой вопрос про "область ценности".
Про «торчащие нитки» ...
Опять же... если кто-то, что-то "видит" - он имеет полное право за это "дергать". И - скажем так - "прикладная задача проектирования" сводится к "надо сделать так, чтобы каждый видел только то, что ему можно дергать". А вы - буквально - говорите "а давайте на это всё забьем" :-)
internal и nested не дает той гибкости и селективности, как данный модификатор.
Вот ваш пример:
class Me
{
[OnlyYou<IFriend>(..)]
public void SomeMethod() { }
}
interface IFriend
{
void UseMe(Me me);
}
Объясните мне, пожалуйста, чем "это" - в принципе - отличается от?
Мой пример:
class Me
{
private void SomeMethod() { }
interface IFriend
{
void UseMe(Me me) => me.SomeMethod();
}
}
Мне вот видится, что - принципиально - ничем. И, имхо, второй вариант "чище", как минимум, как раз из-за того, что ничего не "торчит". Т.е. с мой точки зрения, всё что вы пытаетесь делать в такого рода вариантах - это заменить вполне традиционную технику т.н. mixin типов, на непонятно что :-(
internal работает «по площадям»
Зато для него есть, например, InternalsVisibleToAttribute, который позволяет вытащить "потроха" в отдельную сборку, и уже там сделать "торчать красиво" :-) В смысле, чтоб "торчало" только то, что нужно. И не приводит, при этом, к циклическим зависимостям.
nested не всегда возможен (а если нужен как nested сразу в нескольких классах? а если нужен «взаимный» nested?) и удобен, и он получает доступ сразу ко всем приватным мемберам outer класса (фактически как в плюсах в варианте не function friend), что не есть хорошо.
Ну вы чего?! Через neasted объявляются агрегаты (сильно упрощая - интерфейсы), которые потом используются для "сборки" того, что вам нужно где-то "во вне". Т.е. если вам прям так нужен "слуга двух господ" - вы вполне можете его получить без особых приседаний. И даже в другой сборке, если надо.
Если говорить за "вообще", то приемов работы с tight coupling - мы знаем не мало. В том смысле, что мы знаем "как есть этих слонов". Проблема - ту которую я вижу - заключается в том, что "дружба" - это как раз то, что к этой самой tight coupling приводит.
Про оценку ценности тут, конечно, пусть каждый сам приценяется ...
Если мембер где-то используется, то эта связь так и так есть. Декларация разрешающая ее тут лишь «документирует» этот факт.
Это не так. И, имхо, это достаточно очевидно. Например, достаточно "представить", что "аттрактор" и "друг" это типы из разных сборок. "Лишняя" связь тут же станет очевидной. Нет?
Представить в кавычках, т.к. сильно не уверен, что "оно" умеет в "такое". Код не смотрел. Но без "такого" ценность (и так не очевидная) этой штуки прям сильно уменьшается, имхо.
Через интерфейс
Вот как раз "через интерфейс" исходная задача решается "с точностью до наоборот". В том смысле, что исходная "сущность" - мутабельная, а через реализацию интерфейса предоставляется readonly проекция. Нет? При таком подходе, "грануляция" доступа - пока отбросив её ценность - органично "схлопывается" до агрегатов. Оно может быть и выглядит громоздко на синтетике, но смотрится вполне себе нормально и логично (отбросив, опять же, всё остальное, что можно "предъявить" такому агрегату).
В C++ весь контроль над друзьями находится на стороне класса ..., что по идее более правильно.
Как бы это уже всё давно разжевано не один раз. "Это" - как раз - не правильно. Механизм "дружбы" в плюсах - это следствие, а не причина, так сказать. И если речь о "дружбе" на уровне классов, то реальной ценности там нет. Если мне не изменяет мой склероз, даже Страуструп предлагал смотреть на "дружбу" исключительно с позиции friend function (например, при реализации операторов).
В отличии от плюсов, не можем сделать другом только для конкретного метода другого класса.
Глубокое имхо... ценность такой "гранулярности" - есть следствие "кривоватого" дизайна. Насколько я понимаю, оно может быть хоть сколько-то ценно при работе с - скажем так - перегруженным агрегатом, в качестве аттрактора. Но даже при этом - мы живём в реальном мире, и не отрицаем этого - я бы сильно подумал над тем, нужно ли мне плодить доп. связность, практически, на ровном месте. Есть более "ровные" способы обеспечить такого рода разграничения. Особенно, если речь исключительно о конкретной сборке.
То что приватно и должно оставаться приватным. Тем более что всякого приватного у класса может быть очень много, и выставлять все это хозяйство на показ (как в плюсах) тот еще «эксгибиционизм».
?! Ну т.е. все "минусы" упомянутые вами ранее - это на самом деле не "минусы"? Так? И мы - внезапно - осознаем что из "C++ дружбы" хоть сколько-то ценно только friend function? Или как?
Пусть даже если так. Но в чём ценность такой "дружбы" для C#? "Дать доступ" же не является само целью? Надеюсь. В "плюсах" для этой "штуки" есть вполне конкретная область - операторы - в которой "оно" прям ценно. В том смысле, что другими механиками языка это "не лечится".
А в C# где вы видите эту "область ценности"? Вопрос вызван тем, что - выглядит так - начали мы с одного, а пришли к другому :-) Т.е. решали одну задачу, а решение (которое получилось) - это решение какой-то совсем другой задачи. Вот и интересно - какой? Там у вас есть некоторые "рассуждения на тему". Но это именно что "рассуждения не тему". Можете ли вы переформулировать их в терминах той задачи, которая сформулирована в начале статьи?
Следующий тип случаев с иерархией, реализован так ...
Хм... имхо, это "немножко" не логично. Логично, имхо, требовать для этого случая явной реализации интерфейса. Во избежание, так сказать. Ну и - если уж всё это навеяно "C++ дружбой" - не стоит забывать про "три не". Они не просто так были сформулированы. А из рассуждений про "как должно быть правильно", складывается стойкое ощущение, что про одно из не вы всё время забываете :-(
А предлагается инструмент лишенный их недостатков — новый модификатор доступа, дополняющий стандартную коллекцию модификаторов.
Ну это как сказать :-) Если оно - как я подозреваю (хочу ошибаться) - работает только в рамках конкретной сборки... лично мне вот вообще не понятна ценность этого "инструмента". И даже если представить, что это такой "заход" в т.н. sealed hierarchy - что могло бы иметь ценность, применительно к C#... оно - всё равно - выглядит сильно "странновато" и "не ровно".
Модификаторы позволяют делать так, что группа классов, не только семантически (в голове у разработчика), но теперь и синтаксически образует целостный концепт, без «торчащих ниток» за которые могут дергать кто угодно.
Когда вы говорите про "торчащие нитки", вы вообще о чём? Эти "нитки" они из сборки чтоль "торчат"? Или откуда? Складывается ощущение что вам кто-то строго запретил пользоваться internal (и его производными) уровнем доступа и использовать neasted иерархии. Это, кстати, относится и к приведенным вами "примерам".
Без более строго сформулированного "зачем" - лично у меня - статья оставляет сильное "хлебное" послевкусие :-)
Там же все это уже можно в последних стандартах (тайпклассы, Comparable, ...)
Есть разное "можно". Например, как нам хорошо известно, в любой параметрический полиморфизм "классы типов" можно (легко и просто) добавить через препроцессинг. Но есть разница (с) с тем, когда классы типов это часть системы типов :-)
хотя меня немного коробит просто от пафоса этого словосочетания: "мощность системы типов"
Зря. Лямбда-куб - как наглядная демонстрация той самой "мощности системы типов" - существует уже лет тридцать как.
Всё, на этом возможности системы типов в Java заканчиваются, вы не можете декларативно добавить каких-то ограничений и статически их проверить.
Ну не всё так печально, конечно. В 8-ке появляются лямбды, и "типа" функциональные типы. Что-то получается через них провернуть. В Java 17 появляются "замкнутые" (sealed) иерархии и - соответственно - какие-то "зайчатки" ADT.
Но это всё всегда упирается в фундамент древней (что первично) и "фанатично" номинативной (что вторично, и - по факту - просто следствие древности) системы типов.
Например, нельзя "просто так" добавить в систему типов bottom type, которого там нет в силу "древности". Точнее он там таки есть, но "другой" :-) А без него, и само по себе "печально"... да и какой-нибудь "вывод типов" работает, мягко говоря, "порой странновато".
получается что Джава это язык с бесконечно мощной системой типов.
С точностью до наоборот. В Java весьма примитивная (в основном, конечно, в силу своей древности) система типов. Достаточно вспомнить, например, как типизируется null. Ну или сложности с введением нормальных функциональных типов и те "костылики", которые возникли в типизации (например, лямбд), после введения "аналогов".
"Плюсик" поставил, но не могу - в который уже раз - не заметить, что "очень не очень" рассказывать про "экспериментальную фичу Kotlin" и не указывать "откуда есть" она взялась. Хотя бы - если "долго, сложно" и т.п. - в виде ссылки на соответствующий ей KEEP.
Иначе - как, например, в этом конкретном случае - действительно может быть не понятно "а зачем козе баян?", а представление о "фиче" будет искажено.
Сознательно "так получилось" или нет - но, имхо, из текста статьи действительно сложно вынести что-то отличное от «DI - вид сбоку» и «зависимости, которые неявно передаются при вызове».
А сontext parameters, имхо, вообще про другое ...
Собственно, "контексты" (термин, в отношении Kotlin, ввёл - если мне не изменяет мой склероз, Александр Нозик) были (и есть) в Kotlin с "начала времен". Никто, если я правильно понимаю, "это" специально не придумывал - оно "само так получилось" на стыке двух других "фич".
Первая "фича", это т.н. member extensions - когда ф-ция (или свойство) расширения одного класса, объявляется в теле другого. Речь о такого рода конструкциях:
class A
class B {
fun A.doSome()
}
Вторая - это т.н. scope functions... в частности, те из них, что позволяют "сменить this". Речь о таком вот, примерно:
val a = A()
val b = B()
with(b) {
a.doSome()
}
В данном случае, doSome - это "особое поведение" класса A, в контексте класса B. Почему так? По большому счёту, потому что нет возможности вызвать этот этот doSome "просто так". Вот такое
val a = A()
val b = B()
a.doSome()
по вполне понятным причинам - просто не скомпилируется.
Т.е. для "вызова" такого doSome нужен вполне себе конкретный лексический контекст.
Данный - скажем так - феномен, получил весьма заметное распространение (в библиотечном коде, в реализациях DSL и т.п.) и был описан лет десять назад.
И, прошу заметить, что тут ни о какой "передачи зависимости" (явной или даже неявной) речи вообще не идёт. Контекст - в этом смысле - конструкт чисто "синтаксический", если так можно выразится.
Собственно, какое-то время спустя, появляется KEEP-259. Весь смысл которого сводится к «дайте возможность использовать чужие классы/интерфейсы как этот самый контекст». Речь, при этом, сугубо о синтаксисе (технический аспект тут действительно тривиален), который бы приводил к тому, что - грубо говоря - "использовать без with/run/etc нельзя". Т.е. что бы "оно" приводило к явному (как в случае с исходным феноменом) использованию scope functions для "выделения" контекста в коде.
Так в языке появляются т.н. context receivers и в таком виде "оно" живет до, емнип, двадцать третьего года. Опять же - хотя в этом KEEP явно фиксируется Injecting use case - который таки можно притянуть как "зависимости, которые неявно передаются при вызове" - но он не является основным и сколь-нибудь важным. Инициатива живет и развивается - в основном - в русле т.н. Code Coloring.
И вот где-то к концу 2023 года мы выходим на, собственно, текущий KEEP - т.н. сontext parameters. Который - суть - есть обобщение опыта использования context receivers. Смысл - по большому счёту - остался прежним. Да - многое переосмыслено, и переработано. Да - некоторые вещи изменились радикально. Но мы всё ещё хотим "того же". Если говорить таки про "зависимости, которые неявно передаются при вызове" - то в этом KEEP Dependency injection use case расписан ещё более явно. Но он всё ещё далеко не главный. Более того, некоторые аспекты этого use case - явно не рекомендуется к использованию.
Mandatory не дает fault tolerance, а конфирмов нет
"fault tolerance" чего оно не дает?! Какое такое особое "fault tolerance" дают RMQ'шные basic.ack?!
Они же сами описывают case'ы, когда сообщение будет "доставлено", но при этом ack выгружено не будет. Их там два, емнип... точно есть "нюансы" с persistent. И, насколько я помню, есть "другие нюансы" в случае, когда сообщение должно бы попадать более чем в одну очередь.
Ну и вариант, когда вам прилетает и basic.return, и этот basic.ack - для одного и того же сообщения - в любом случае, надо уметь "держать в уме", имхо :-)
Вообще, интересно... я точно помню, что раньше, вместе со спеками публиковались отдельные доп. документы: RoI по клиенту/серверу и т.п. Но вот полез на OASIS... и спеки без "тулчейнов", и "допников" не обнаружил :-( Теперь даже в RoI "непотыкаешь" :-(
Если у нас нет подтверждения доставки - мы просто не можем ничего гарантировать
Для ясности... мы оба понимаем, что никакие return и ack/nack от "чудес по сети" ну никак не спасают. Так? Если мы говорим о "девятке" то - по спеке - при обнаружении "чудес", нужно asap:
По возможность пульнуть (куда только можно) channel.flow
Кидать соответствующее исключение, и закрывать (переоткрывать) соответствующие каналы, соединения.
Это означает (ко всему прочему), что надо быть готовыми и к тому, что ни ack, ни nack получен не будет. Я это к тому, что если сама реализация клиента не умеет в обработку "такого", то обрабатывать "такое" придется самим. При реализации outbox, или ещё где-то.
"Стандартный" клиент (я для этого RoI и пытался найти) - если я правильно помню - должен на каждый канал держать отдельный "буфер". Он нужен для того, чтобы при "переоткрытии" канала не начинать "жизнь с нуля". Можно - наверное - раскопать клиента apache qpid... они, емнип, были весьма щепетильны в следовании RoI.
А так - если следовать принципу success is silent, and failure is noisy - достаточно же при получении basic.return помечать соответствующее сообщение в outbox как "нужно отправить". Нет?
С одной стороны, какая-то часть утверждений верная... при этом, другая часть - вызывает недоумение :-(
Метод отправки сообщения basic.publish не имеет возвратной части по протоколу AMQP 0-9-1 ...
Это так, в том смысле, что - условного - basic.publish-ok - по протоколу AMQP - действительно нет.
У каждого такого метода есть возвратная часть, которая говорит some-method-ok.
Это, мягко говоря, не так. Basic.publish тут не уникален - полно в протоколе методов без "возвратной части". Даже в классе basic он такой не один. Даже в множестве клиентских команд.
То есть наш basic.publish работает как fire and forget в семантике at most once.
А вот это вот - вообще не так. Механизмы контроля "этого" были ещё в "восьмерке", остались в "девятке"... и только в "десятке" эта часть была кардинально "пересмотрена" в сторону симметричности обмена. Ну так вся "десятка" такая.
Моя основная идея о причинах такого дизайна протокола AMQP в том, что ...
А зачем гадать?! Если вы таки читали спецификации, про "причины такого дизайна" есть прям в самом её начале. Пункт 2.2.3, если уж совсем точно. Можно почитать.
Тогда в протоколе должна быть другая часть, посвященная гарантированной доставке с подтверждениями. И да, она есть – транзакции.
"Другая часть" - в смысле "гарантированной доставки" - действительно есть. И транзакции есть - но они, как вы сами и говорите, про "другое". А вот "подтверждений" - в части basic.publish - действительно нет. By design.
Как это решили ребята из RabbitMQ
"Ребята из RabbitMQ" решили "подзабить" на спеку, в этой части... о чём им говорили, ещё когда они на "восьмерке" сидели.
Смотрим, что нам говорит "спека":
При обнаружении "сетевых проблем" клиент должен "закрывать" канал или соединение целиком;
Брокер, в случае невозможности обслуживать канал, переводит его в inactive через channel.flow;
В basic.publish есть два "флага", которые отвечают за обработку negative case'ов "доставки" на стороне брокера. Можно глянуть спеку, но суть в том, что при наличие этих флагов, брокер должен "вернуть" basic.return для каждого такого сообщения, в случае наступления этих самых negative case'ов.
Там, конечно, в некоторых местах MAY, а не MUST, но та ситуация, что описывается в доке RMQ как "танцы" с их basic.ack/basic.nack вполне может выглядеть как:
Для всех basic.publish с флагами mandatory/immediate - сформировать соответствующих basic.return и направить его в канал соответствующего клиента;
Направить в этот канал cannel.flow c acticve = 0.
без каких-либо противоречий "спеке". Другое дело, что "внутре у неё неонка" - в смысле, есть различные "особенности реализации", скажем так.
И то, что "ребята из RabbitMQ"- емнип, только с выходом "десятки" - часть её "сессионной" механики попытались адаптировать к - это лучше, конечно, чем ничего. До этого-то эта часть "спеки" ими вообще игнорировалась. При том, что сам по себе basic.return был уже реализован, емнип. Но отсылали они его (да и отсылают) только в тех случаях, когда им это "удобно", насколько я помню. Но, имхо, лучше бы уж целиком на "десятку" - в свое время - пересели... гарантий доставки-то, этот их "костылик" всё равно ведь не дает. О чем они сами и говорят. И да - это особенность RMQ, но не AMQP.
С VMными - всё понятно. Они более менее вписываются по "фазам" + их зависимости разрешаются через maven. Но даже тут есть нюансы...
У kotlin с использованием "плюшек" под maven уже есть "проблемы". Тот же ksp - насколько я в курсе - пока в maven нормально "вписать" не удалось. Оф. рекомендация, емнип, запускайте через exec.
Остальное всё - если я правильно понимаю - это всё на уровне "давайте запускать kotlinc через ant-task из maven". Можно, конечно - но в чём смысл-то?! Тут же нет ни управления зависимостями, ни всего остального... это всё вне maven получается. Нет?
Честно говоря - если брать gradle, то там большинство "таких" плагинов тож строятся по этому принципу (в смысле, "давайте запускать ..."). Но в gradle хоть в принципе возможно затащить "всё это" в билд скрипт, и реально "рулить" этим. Как тож самое (пусть хоть гипотетически) проворачивать в maven - лично мне - вообще не понятно :-(
Мы, когда пытались "это" использовать, знатно об это "постукались". Плюс, была ещё странность со сменой кофнигов... которая "то работает, то не работает".
Справедливости ради у нас на проекте (3M+ LOC, 800+ модулей) своя немного кастомная версия.
Ну мы тож пытались это под себя "допилить". В конце концов, "плюнули и забыли" (tm)
К сожалению, про "самое сладенькое" (Maven Artifact Resolver) в статье только пара строчек. А меж тем - если мне не изменяет мой склероз - избавление от Aether и настраиваемый resolver - декларировались одними из главных целей, которые "четверка" должна будет достичь.
Сделано там, действительно, не мало. Но пока, следить за подвижками в этом плане можно только по фотографиисходникам.
Надеюсь "апачи" таки разродятся статьей на этот счёт, и мы таки узнаем много нового и интересного.
Я дико извиняюсь... но, вы сами-то пробовали "этим" пользоваться? Особенно - в CI/CD окружении? Судя по всему - нет. Иначе бы знали о "небольшом баге", который висит чуть ли не со старта на этой "балалайке". Грубо говоря - если вы сидите на "тройке" - что есть этот "кэш" у вас, что его нет.
FYI. Нет нормального кэширования в maven. By desing нет. Даже в "четверке" - это не предусмотрено. А попытки "прикрутить сбоку" - это (хоть так верти, хоть эдак) именно что "костыли со стразами".
Мы - например - и "своё" пытались делать, и эту "балалайку" прикручивали... не работает оно так, как надо. И - в общем случае - не может работать чисто из-за "некоторых особенностей" самого "движка".
:-) "Нечитаемая хрень" - это многоэтажные match'и от которых в принципе не уйти, как только union types начинают "оккупировать наш код".
@Dhwtj уже привел пример... он не слишком удачный, конечно - там вообще нет union... и это всё - более-менее - "съедобно", но "идея" - я думаю - должна быть ясна :-)
Зачем вообще нужен union-type? Очевидно, чтобы выражать "штуки" типа
t<T> -> t<t<T>> -> t<T>
Условно, ну хочется мне чтоб
Option<Option<T>> = Option<T>
и "вот это вот всё", что в принципе не выражается через sum-type.
И тут же вылезает "проблемка"... union-type - by design - некомпозициональный тип. Это - само по себе - значит не мало, но - ко всему прочему - означает так же, что "номинативно" работать с элементами такого типа - неудобно. А со структурной типизацией у jvm возможности, прямо скажем, весьма ограничены. Соответственно, мы гарантированно проваливаемся в match там, где - казалось бы - это и не нужно.
Да, можно сослаться, например, на какой-нибудь reflectiveSelectable... и прочее. Но все эти "штуки" - жрут как не в себя и - соответственно - "на наш путь" :-)
Ну и пошло-поехало... Мыж умные. Мыж не просто так, выбрали union (а не sum, например). У нас причина есть :-) И с огромной долей вероятности, элементами нашего супер-пупер-AA-union-type будут оказываться не менее гига-мега-AA-union-type и т.д. Ну это просто действительно удобно - обобщать через union.
Но вот разбираться с результатом этого обобщения (полученного, например, в результате композиции) в отсутствии структурной типизации - мягко говоря, тяжко.
А чтоб "не было тяжко" - приходится делать над собой усилие и бдеть :-) И других вариантов, к сожалению, просто нет.
При этом, то, что предлагают ребята из Kotlin - лично для меня - не выглядит сколь-нибудь страшно. Просто по той причине, что все элементы, в предлагаемом типе (кроме одного... которого может и не быть) - по определению: имеют эквивалентную семантику (это ошибки), не могут образовывать собственной иерархии (они не open, не могут реализовывать интерфейсы и не могут быть параметризированы) и не имеют пересечений с остальными типами (у них отдельный top type). Т.е., грубо говоря, самая "страшная" часть этого union - гарантированно "плоская". А "как есть таких слонов" (tm) - мы прекрасно знаем.
Да - остается опасность "обобщающего элемента" (в слайдах про "это" ничего нет). Но, я думаю, что там парни "не глупее нас" - самое простое, имхо, тупо запретить включать сам Error в множество.
P.S. На всякий... честно - мне лень тут приводить листинги чтоб "на пальцах" продемонстрировать вам, то, о чём написано выше. Имхо, всё что выше - это и так жуткий оффтопик. Но, судя по всему, scala для вас "родное", а значит - вы и сами должны прекрасно себе представлять все эти "прелести".
Если коротко - каноничный AA-union-type штука сильно уж - скажем так - "обобщающая". Во всех смыслах этого слова.
И очень уж много нужно контроля (на уровне "усилий мозга"), чтобы пользоваться этим "правильно". А чуть "отвлекся" - и всё... "получилась какая-то нечитаемая хрень" :-(
И ещё больше усилий нужно для возврата в "контекст". Читать "это", порой, совсем "грустненько"... хотя и сам "это вчера только написал" :-)
Не понял как "кидается" ошибка. Через throw или через return?
throw никак не меняется. Соответственно, не имеет смысла для подтипов Error. Т.е. "ошибка" возвращается штатно - через return.
Могу ли я создать функцию, которая возвращает и кидает ошибку того же класса одновременно?
Нет. Но если очень хочется, через !! можно кинуть KotlinException, который "обернет" Error.
Если да, то как понять был ли это успешный возврат или ошибка?
Все "ошибки" - это подтипы Error. Error - это новый отдельный top type. Т.е. и он, и его подтипы не приводятся к Any?. Соответственно, на "или ошибка" можно проверять, банально, через is Error.
Пытаться тянуть Result до самого Spring MVC — плохая идея
"Прикол" в том, что тут как раз нет Result'а. Ни в терминах rust'а, ни в терминах kotlin'а.
Result - это "эвфемизм" более общего Either. А Either - by design - скажем так, "хромает" в плане композиции "левой" своей части. Т.е. "левая" часть композиции выводится в что-то осмысленное только при прям очень сильных ограничениях, накладываемых на. Чего - по понятным причинам - делать вообще не хочется.
Result - соответственно - "хромает" на "правую" свою часть.
То, что предлагается ребятами из kotlin - позволяет с одной стороны - избежать такого рода "хромоты". А с другой - не скатится в AA-union-hell.
Насколько - в реальности - окажутся сильными ограничения, накладываемые на подтипы Error - это будем делать посмотреть (с). Дизайн пока не финализирован. Но то, что есть сейчас не выглядит сколь-нибудь "страшным".
Я к тому, что иметь - условный -
private val <T:Any> (T|Error).responseEntity: ResponseEntity<out Any> =
get() -> when { ...}
на уровне контроллера (или даже его пакета) - не выглядит, имхо, чем-то прям "ужос-ужос". Error и upper-bound - понятно, "в реальности" заменяются на какие-то более осмысленные type aliase'ы.
А вот как раз "городьба" отдельной иерархии для передачи "типизации в ошибках" в контроллер - при условии наличия rich errors - будет выглядеть "немножко странно". Нет?
Яб таки дождался финализации. Возможно, оно действительно "негативненько" скажется на interop'е со стороны Java. Возможно (ну а вдруг), придумают как таки обойтись - в этой части - без "костыликов". Но это пока единственное, что хоть сколько-то "пугает".
Я это уже читал, но так и не понял в чём конкретно эта "польза". Другими словами, я не понимаю, каким образом дополнительная связность (хоть её наличие вы и отрицаете) может быть полезна. Особенно, в "проектах с большим, сложным внутреннем API".
Ну я таки глянул код. Позвольте усомниться в этом вашем утверждении. Если аттрактор и друг это типы из разных сборок, то возникает циклическая зависимость между сборками. Причина - та самая "лишняя" связность, наличие которой вы отрицаете, но которой, тем не менее, нет в канонических вариантах решения.
Собственно, это (наличие циклической зависимости) было очевидно и без кода... но я надеялся, что у вас там она разрешается, например, через генерацию отдельной сборки. Я ошибался.
При традиционных подходах к - та часть, что в вашем варианте становится аттрактором ничего не знает о "дружественной" части.
Я это понял :-) Моя реплика была про то, что каноническое решение выглядит с точностью до наоборот. Если хотите, то вопрос в том, почему у вас не так? Почему за основу взят - так сказать - "вывернутый" вариант?
Как я понимаю, мы о разных "трех не" говорим. Речь была о том, что "дружба" не наследуется. Не просто так. Очевидно, что - как минимум - в отношении к интерфейсам у вас "всё не так". Я, собственно, поэтому и упомянул явную реализацию.
К сожалению, там нет "обоснования зачем". Там есть "смотри как могу". Любой из рассмотренных примеров замечательно решается без "этого". Собственно, в большинстве случаев, вы сами же об этом и пишите. Поэтому и был мой вопрос про "область ценности".
Опять же... если кто-то, что-то "видит" - он имеет полное право за это "дергать". И - скажем так - "прикладная задача проектирования" сводится к "надо сделать так, чтобы каждый видел только то, что ему можно дергать". А вы - буквально - говорите "а давайте на это всё забьем" :-)
Вот ваш пример:
Объясните мне, пожалуйста, чем "это" - в принципе - отличается от?
Мой пример:
Мне вот видится, что - принципиально - ничем. И, имхо, второй вариант "чище", как минимум, как раз из-за того, что ничего не "торчит". Т.е. с мой точки зрения, всё что вы пытаетесь делать в такого рода вариантах - это заменить вполне традиционную технику т.н. mixin типов, на непонятно что :-(
Зато для него есть, например, InternalsVisibleToAttribute, который позволяет вытащить "потроха" в отдельную сборку, и уже там сделать "торчать красиво" :-) В смысле, чтоб "торчало" только то, что нужно. И не приводит, при этом, к циклическим зависимостям.
Ну вы чего?! Через neasted объявляются агрегаты (сильно упрощая - интерфейсы), которые потом используются для "сборки" того, что вам нужно где-то "во вне". Т.е. если вам прям так нужен "слуга двух господ" - вы вполне можете его получить без особых приседаний. И даже в другой сборке, если надо.
Если говорить за "вообще", то приемов работы с tight coupling - мы знаем не мало. В том смысле, что мы знаем "как есть этих слонов". Проблема - ту которую я вижу - заключается в том, что "дружба" - это как раз то, что к этой самой tight coupling приводит.
dixi :-)
Доброе...
Начну с середины
Это не так. И, имхо, это достаточно очевидно. Например, достаточно "представить", что "аттрактор" и "друг" это типы из разных сборок. "Лишняя" связь тут же станет очевидной. Нет?
Представить в кавычках, т.к. сильно не уверен, что "оно" умеет в "такое". Код не смотрел. Но без "такого" ценность (и так не очевидная) этой штуки прям сильно уменьшается, имхо.
Вот как раз "через интерфейс" исходная задача решается "с точностью до наоборот". В том смысле, что исходная "сущность" - мутабельная, а через реализацию интерфейса предоставляется readonly проекция. Нет? При таком подходе, "грануляция" доступа - пока отбросив её ценность - органично "схлопывается" до агрегатов. Оно может быть и выглядит громоздко на синтетике, но смотрится вполне себе нормально и логично (отбросив, опять же, всё остальное, что можно "предъявить" такому агрегату).
Как бы это уже всё давно разжевано не один раз. "Это" - как раз - не правильно. Механизм "дружбы" в плюсах - это следствие, а не причина, так сказать. И если речь о "дружбе" на уровне классов, то реальной ценности там нет. Если мне не изменяет мой склероз, даже Страуструп предлагал смотреть на "дружбу" исключительно с позиции friend function (например, при реализации операторов).
Глубокое имхо... ценность такой "гранулярности" - есть следствие "кривоватого" дизайна. Насколько я понимаю, оно может быть хоть сколько-то ценно при работе с - скажем так - перегруженным агрегатом, в качестве аттрактора. Но даже при этом - мы живём в реальном мире, и не отрицаем этого - я бы сильно подумал над тем, нужно ли мне плодить доп. связность, практически, на ровном месте. Есть более "ровные" способы обеспечить такого рода разграничения. Особенно, если речь исключительно о конкретной сборке.
?! Ну т.е. все "минусы" упомянутые вами ранее - это на самом деле не "минусы"? Так? И мы - внезапно - осознаем что из "C++ дружбы" хоть сколько-то ценно только friend function? Или как?
Пусть даже если так. Но в чём ценность такой "дружбы" для C#? "Дать доступ" же не является само целью? Надеюсь. В "плюсах" для этой "штуки" есть вполне конкретная область - операторы - в которой "оно" прям ценно. В том смысле, что другими механиками языка это "не лечится".
А в C# где вы видите эту "область ценности"? Вопрос вызван тем, что - выглядит так - начали мы с одного, а пришли к другому :-) Т.е. решали одну задачу, а решение (которое получилось) - это решение какой-то совсем другой задачи. Вот и интересно - какой? Там у вас есть некоторые "рассуждения на тему". Но это именно что "рассуждения не тему". Можете ли вы переформулировать их в терминах той задачи, которая сформулирована в начале статьи?
Хм... имхо, это "немножко" не логично. Логично, имхо, требовать для этого случая явной реализации интерфейса. Во избежание, так сказать. Ну и - если уж всё это навеяно "C++ дружбой" - не стоит забывать про "три не". Они не просто так были сформулированы. А из рассуждений про "как должно быть правильно", складывается стойкое ощущение, что про одно из не вы всё время забываете :-(
Ну это как сказать :-) Если оно - как я подозреваю (хочу ошибаться) - работает только в рамках конкретной сборки... лично мне вот вообще не понятна ценность этого "инструмента". И даже если представить, что это такой "заход" в т.н. sealed hierarchy - что могло бы иметь ценность, применительно к C#... оно - всё равно - выглядит сильно "странновато" и "не ровно".
Когда вы говорите про "торчащие нитки", вы вообще о чём? Эти "нитки" они из сборки чтоль "торчат"? Или откуда? Складывается ощущение что вам кто-то строго запретил пользоваться internal (и его производными) уровнем доступа и использовать neasted иерархии. Это, кстати, относится и к приведенным вами "примерам".
Без более строго сформулированного "зачем" - лично у меня - статья оставляет сильное "хлебное" послевкусие :-)
Есть разное "можно". Например, как нам хорошо известно, в любой параметрический полиморфизм "классы типов" можно (легко и просто) добавить через препроцессинг. Но есть разница (с) с тем, когда классы типов это часть системы типов :-)
Зря. Лямбда-куб - как наглядная демонстрация той самой "мощности системы типов" - существует уже лет тридцать как.
Ну не всё так печально, конечно. В 8-ке появляются лямбды, и "типа" функциональные типы. Что-то получается через них провернуть. В Java 17 появляются "замкнутые" (sealed) иерархии и - соответственно - какие-то "зайчатки" ADT.
Но это всё всегда упирается в фундамент древней (что первично) и "фанатично" номинативной (что вторично, и - по факту - просто следствие древности) системы типов.
Например, нельзя "просто так" добавить в систему типов bottom type, которого там нет в силу "древности". Точнее он там таки есть, но "другой" :-) А без него, и само по себе "печально"... да и какой-нибудь "вывод типов" работает, мягко говоря, "порой странновато".
С точностью до наоборот. В Java весьма примитивная (в основном, конечно, в силу своей древности) система типов. Достаточно вспомнить, например, как типизируется null. Ну или сложности с введением нормальных функциональных типов и те "костылики", которые возникли в типизации (например, лямбд), после введения "аналогов".
Доброе...
"Плюсик" поставил, но не могу - в который уже раз - не заметить, что "очень не очень" рассказывать про "экспериментальную фичу Kotlin" и не указывать "откуда есть" она взялась. Хотя бы - если "долго, сложно" и т.п. - в виде ссылки на соответствующий ей KEEP.
Иначе - как, например, в этом конкретном случае - действительно может быть не понятно "а зачем козе баян?", а представление о "фиче" будет искажено.
Сознательно "так получилось" или нет - но, имхо, из текста статьи действительно сложно вынести что-то отличное от «DI - вид сбоку» и «зависимости, которые неявно передаются при вызове».
А сontext parameters, имхо, вообще про другое ...
Собственно, "контексты" (термин, в отношении Kotlin, ввёл - если мне не изменяет мой склероз, Александр Нозик) были (и есть) в Kotlin с "начала времен". Никто, если я правильно понимаю, "это" специально не придумывал - оно "само так получилось" на стыке двух других "фич".
Первая "фича", это т.н. member extensions - когда ф-ция (или свойство) расширения одного класса, объявляется в теле другого. Речь о такого рода конструкциях:
Вторая - это т.н. scope functions... в частности, те из них, что позволяют "сменить this". Речь о таком вот, примерно:
В данном случае, doSome - это "особое поведение" класса A, в контексте класса B. Почему так? По большому счёту, потому что нет возможности вызвать этот этот doSome "просто так". Вот такое
по вполне понятным причинам - просто не скомпилируется.
Т.е. для "вызова" такого doSome нужен вполне себе конкретный лексический контекст.
Данный - скажем так - феномен, получил весьма заметное распространение (в библиотечном коде, в реализациях DSL и т.п.) и был описан лет десять назад.
И, прошу заметить, что тут ни о какой "передачи зависимости" (явной или даже неявной) речи вообще не идёт. Контекст - в этом смысле - конструкт чисто "синтаксический", если так можно выразится.
Собственно, какое-то время спустя, появляется KEEP-259. Весь смысл которого сводится к «дайте возможность использовать чужие классы/интерфейсы как этот самый контекст». Речь, при этом, сугубо о синтаксисе (технический аспект тут действительно тривиален), который бы приводил к тому, что - грубо говоря - "использовать без with/run/etc нельзя". Т.е. что бы "оно" приводило к явному (как в случае с исходным феноменом) использованию scope functions для "выделения" контекста в коде.
Так в языке появляются т.н. context receivers и в таком виде "оно" живет до, емнип, двадцать третьего года. Опять же - хотя в этом KEEP явно фиксируется Injecting use case - который таки можно притянуть как "зависимости, которые неявно передаются при вызове" - но он не является основным и сколь-нибудь важным. Инициатива живет и развивается - в основном - в русле т.н. Code Coloring.
И вот где-то к концу 2023 года мы выходим на, собственно, текущий KEEP - т.н. сontext parameters. Который - суть - есть обобщение опыта использования context receivers. Смысл - по большому счёту - остался прежним. Да - многое переосмыслено, и переработано. Да - некоторые вещи изменились радикально. Но мы всё ещё хотим "того же". Если говорить таки про "зависимости, которые неявно передаются при вызове" - то в этом KEEP Dependency injection use case расписан ещё более явно. Но он всё ещё далеко не главный. Более того, некоторые аспекты этого use case - явно не рекомендуется к использованию.
Вот, кстати, "дико плюсую" за "по уму". Сам-то ETF простой как три копейки... но вот дальше.
Отлично получилось, в целом. Осталось реализовать ETF (ну и ещё "койчего") и можно цепляться как external node к.
"fault tolerance" чего оно не дает?! Какое такое особое "fault tolerance" дают RMQ'шные basic.ack?!
Они же сами описывают case'ы, когда сообщение будет "доставлено", но при этом ack выгружено не будет. Их там два, емнип... точно есть "нюансы" с persistent. И, насколько я помню, есть "другие нюансы" в случае, когда сообщение должно бы попадать более чем в одну очередь.
Ну и вариант, когда вам прилетает и basic.return, и этот basic.ack - для одного и того же сообщения - в любом случае, надо уметь "держать в уме", имхо :-)
Вообще, интересно... я точно помню, что раньше, вместе со спеками публиковались отдельные доп. документы: RoI по клиенту/серверу и т.п. Но вот полез на OASIS... и спеки без "тулчейнов", и "допников" не обнаружил :-( Теперь даже в RoI "непотыкаешь" :-(
Для ясности... мы оба понимаем, что никакие return и ack/nack от "чудес по сети" ну никак не спасают. Так? Если мы говорим о "девятке" то - по спеке - при обнаружении "чудес", нужно asap:
По возможность пульнуть (куда только можно) channel.flow
Кидать соответствующее исключение, и закрывать (переоткрывать) соответствующие каналы, соединения.
Это означает (ко всему прочему), что надо быть готовыми и к тому, что ни ack, ни nack получен не будет. Я это к тому, что если сама реализация клиента не умеет в обработку "такого", то обрабатывать "такое" придется самим. При реализации outbox, или ещё где-то.
"Стандартный" клиент (я для этого RoI и пытался найти) - если я правильно помню - должен на каждый канал держать отдельный "буфер". Он нужен для того, чтобы при "переоткрытии" канала не начинать "жизнь с нуля". Можно - наверное - раскопать клиента apache qpid... они, емнип, были весьма щепетильны в следовании RoI.
А так - если следовать принципу success is silent, and failure is noisy - достаточно же при получении basic.return помечать соответствующее сообщение в outbox как "нужно отправить". Нет?
Очень странное впечатление от статьи.
С одной стороны, какая-то часть утверждений верная... при этом, другая часть - вызывает недоумение :-(
Это так, в том смысле, что - условного - basic.publish-ok - по протоколу AMQP - действительно нет.
Это, мягко говоря, не так. Basic.publish тут не уникален - полно в протоколе методов без "возвратной части". Даже в классе basic он такой не один. Даже в множестве клиентских команд.
А вот это вот - вообще не так. Механизмы контроля "этого" были ещё в "восьмерке", остались в "девятке"... и только в "десятке" эта часть была кардинально "пересмотрена" в сторону симметричности обмена. Ну так вся "десятка" такая.
А зачем гадать?! Если вы таки читали спецификации, про "причины такого дизайна" есть прям в самом её начале. Пункт 2.2.3, если уж совсем точно. Можно почитать.
"Другая часть" - в смысле "гарантированной доставки" - действительно есть. И транзакции есть - но они, как вы сами и говорите, про "другое". А вот "подтверждений" - в части basic.publish - действительно нет. By design.
"Ребята из RabbitMQ" решили "подзабить" на спеку, в этой части... о чём им говорили, ещё когда они на "восьмерке" сидели.
Смотрим, что нам говорит "спека":
При обнаружении "сетевых проблем" клиент должен "закрывать" канал или соединение целиком;
Брокер, в случае невозможности обслуживать канал, переводит его в inactive через channel.flow;
В basic.publish есть два "флага", которые отвечают за обработку negative case'ов "доставки" на стороне брокера. Можно глянуть спеку, но суть в том, что при наличие этих флагов, брокер должен "вернуть" basic.return для каждого такого сообщения, в случае наступления этих самых negative case'ов.
Там, конечно, в некоторых местах MAY, а не MUST, но та ситуация, что описывается в доке RMQ как "танцы" с их basic.ack/basic.nack вполне может выглядеть как:
Для всех basic.publish с флагами mandatory/immediate - сформировать соответствующих basic.return и направить его в канал соответствующего клиента;
Направить в этот канал cannel.flow c acticve = 0.
без каких-либо противоречий "спеке". Другое дело, что "внутре у неё неонка" - в смысле, есть различные "особенности реализации", скажем так.
И то, что "ребята из RabbitMQ"- емнип, только с выходом "десятки" - часть её "сессионной" механики попытались адаптировать к - это лучше, конечно, чем ничего. До этого-то эта часть "спеки" ими вообще игнорировалась. При том, что сам по себе basic.return был уже реализован, емнип. Но отсылали они его (да и отсылают) только в тех случаях, когда им это "удобно", насколько я помню. Но, имхо, лучше бы уж целиком на "десятку" - в свое время - пересели... гарантий доставки-то, этот их "костылик" всё равно ведь не дает. О чем они сами и говорят. И да - это особенность RMQ, но не AMQP.
С VMными - всё понятно. Они более менее вписываются по "фазам" + их зависимости разрешаются через maven. Но даже тут есть нюансы...
У kotlin с использованием "плюшек" под maven уже есть "проблемы". Тот же ksp - насколько я в курсе - пока в maven нормально "вписать" не удалось. Оф. рекомендация, емнип, запускайте через exec.
Остальное всё - если я правильно понимаю - это всё на уровне "давайте запускать kotlinc через ant-task из maven". Можно, конечно - но в чём смысл-то?! Тут же нет ни управления зависимостями, ни всего остального... это всё вне maven получается. Нет?
Честно говоря - если брать gradle, то там большинство "таких" плагинов тож строятся по этому принципу (в смысле, "давайте запускать ..."). Но в gradle хоть в принципе возможно затащить "всё это" в билд скрипт, и реально "рулить" этим. Как тож самое (пусть хоть гипотетически) проворачивать в maven - лично мне - вообще не понятно :-(
https://github.com/apache/maven-build-cache-extension/issues/270 - это тот самый "небольшой баг".
Мы, когда пытались "это" использовать, знатно об это "постукались". Плюс, была ещё странность со сменой кофнигов... которая "то работает, то не работает".
Ну мы тож пытались это под себя "допилить". В конце концов, "плюнули и забыли" (tm)
К сожалению, про "самое сладенькое" (Maven Artifact Resolver) в статье только пара строчек. А меж тем - если мне не изменяет мой склероз - избавление от Aether и настраиваемый resolver - декларировались одними из главных целей, которые "четверка" должна будет достичь.
Сделано там, действительно, не мало. Но пока, следить за подвижками в этом плане можно только по
фотографиисходникам.Надеюсь "апачи" таки разродятся статьей на этот счёт, и мы таки узнаем много нового и интересного.
Не только для сборки? Или не только для Java проектов?
Если таки второе, то интересно - как?! Через antrun какой-нибудь? Так-то оно гвоздиками всё прибито к.
Я дико извиняюсь... но, вы сами-то пробовали "этим" пользоваться? Особенно - в CI/CD окружении? Судя по всему - нет. Иначе бы знали о "небольшом баге", который висит чуть ли не со старта на этой "балалайке". Грубо говоря - если вы сидите на "тройке" - что есть этот "кэш" у вас, что его нет.
FYI. Нет нормального кэширования в maven. By desing нет. Даже в "четверке" - это не предусмотрено. А попытки "прикрутить сбоку" - это (хоть так верти, хоть эдак) именно что "костыли со стразами".
Мы - например - и "своё" пытались делать, и эту "балалайку" прикручивали... не работает оно так, как надо. И - в общем случае - не может работать чисто из-за "некоторых особенностей" самого "движка".
:-) "Нечитаемая хрень" - это многоэтажные match'и от которых в принципе не уйти, как только union types начинают "оккупировать наш код".
@Dhwtj уже привел пример... он не слишком удачный, конечно - там вообще нет union... и это всё - более-менее - "съедобно", но "идея" - я думаю - должна быть ясна :-)
Зачем вообще нужен union-type? Очевидно, чтобы выражать "штуки" типа
Условно, ну хочется мне чтоб
и "вот это вот всё", что в принципе не выражается через sum-type.
И тут же вылезает "проблемка"... union-type - by design - некомпозициональный тип. Это - само по себе - значит не мало, но - ко всему прочему - означает так же, что "номинативно" работать с элементами такого типа - неудобно. А со структурной типизацией у jvm возможности, прямо скажем, весьма ограничены. Соответственно, мы гарантированно проваливаемся в match там, где - казалось бы - это и не нужно.
Да, можно сослаться, например, на какой-нибудь reflectiveSelectable... и прочее. Но все эти "штуки" - жрут как не в себя и - соответственно - "на наш путь" :-)
Ну и пошло-поехало... Мыж умные. Мыж не просто так, выбрали union (а не sum, например). У нас причина есть :-) И с огромной долей вероятности, элементами нашего супер-пупер-AA-union-type будут оказываться не менее гига-мега-AA-union-type и т.д. Ну это просто действительно удобно - обобщать через union.
Но вот разбираться с результатом этого обобщения (полученного, например, в результате композиции) в отсутствии структурной типизации - мягко говоря, тяжко.
А чтоб "не было тяжко" - приходится делать над собой усилие и бдеть :-) И других вариантов, к сожалению, просто нет.
При этом, то, что предлагают ребята из Kotlin - лично для меня - не выглядит сколь-нибудь страшно. Просто по той причине, что все элементы, в предлагаемом типе (кроме одного... которого может и не быть) - по определению: имеют эквивалентную семантику (это ошибки), не могут образовывать собственной иерархии (они не open, не могут реализовывать интерфейсы и не могут быть параметризированы) и не имеют пересечений с остальными типами (у них отдельный top type). Т.е., грубо говоря, самая "страшная" часть этого union - гарантированно "плоская". А "как есть таких слонов" (tm) - мы прекрасно знаем.
Да - остается опасность "обобщающего элемента" (в слайдах про "это" ничего нет). Но, я думаю, что там парни "не глупее нас" - самое простое, имхо, тупо запретить включать сам Error в множество.
P.S. На всякий... честно - мне лень тут приводить листинги чтоб "на пальцах" продемонстрировать вам, то, о чём написано выше. Имхо, всё что выше - это и так жуткий оффтопик. Но, судя по всему, scala для вас "родное", а значит - вы и сами должны прекрасно себе представлять все эти "прелести".
Если коротко - каноничный AA-union-type штука сильно уж - скажем так - "обобщающая". Во всех смыслах этого слова.
И очень уж много нужно контроля (на уровне "усилий мозга"), чтобы пользоваться этим "правильно". А чуть "отвлекся" - и всё... "получилась какая-то нечитаемая хрень" :-(
И ещё больше усилий нужно для возврата в "контекст". Читать "это", порой, совсем "грустненько"... хотя и сам "это вчера только написал" :-)
слайды по докладу
throw никак не меняется. Соответственно, не имеет смысла для подтипов Error. Т.е. "ошибка" возвращается штатно - через return.
Нет. Но если очень хочется, через !! можно кинуть KotlinException, который "обернет" Error.
Все "ошибки" - это подтипы Error. Error - это новый отдельный top type. Т.е. и он, и его подтипы не приводятся к Any?. Соответственно, на "или ошибка" можно проверять, банально, через is Error.
"Прикол" в том, что тут как раз нет Result'а. Ни в терминах rust'а, ни в терминах kotlin'а.
Result - это "эвфемизм" более общего Either. А Either - by design - скажем так, "хромает" в плане композиции "левой" своей части. Т.е. "левая" часть композиции выводится в что-то осмысленное только при прям очень сильных ограничениях, накладываемых на. Чего - по понятным причинам - делать вообще не хочется.
Result - соответственно - "хромает" на "правую" свою часть.
То, что предлагается ребятами из kotlin - позволяет с одной стороны - избежать такого рода "хромоты". А с другой - не скатится в AA-union-hell.
Насколько - в реальности - окажутся сильными ограничения, накладываемые на подтипы Error - это будем делать посмотреть (с). Дизайн пока не финализирован. Но то, что есть сейчас не выглядит сколь-нибудь "страшным".
Я к тому, что иметь - условный -
на уровне контроллера (или даже его пакета) - не выглядит, имхо, чем-то прям "ужос-ужос". Error и upper-bound - понятно, "в реальности" заменяются на какие-то более осмысленные type aliase'ы.
А вот как раз "городьба" отдельной иерархии для передачи "типизации в ошибках" в контроллер - при условии наличия rich errors - будет выглядеть "немножко странно". Нет?
Сугубое имхо...
Выглядит оно уже сейчас сильно "вкусно". Ребята из arrow - уже облизываются :-) Наконец-то нормальная "человечья" error composition "из коробки".
Озвученные "сомнения" - в разрезе Kotlin - выглядят, мягко говоря, неубедительно.
Яб таки дождался финализации. Возможно, оно действительно "негативненько" скажется на interop'е со стороны Java. Возможно (ну а вдруг), придумают как таки обойтись - в этой части - без "костыликов". Но это пока единственное, что хоть сколько-то "пугает".