Здравствуйте, vsb, Вы писали:
vsb>Здравствуйте, vaa, Вы писали:
vsb>>>А если файл запускать, а не в репле?
vaa>>"multiple definitions of identifier"
vsb>Видимо какие-то особенности репла.
vsb>А если в файле написать:
vsb>
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Sinclair, Вы писали:
·>Теоретически, конечно, это можно всё переписать функционально, на иммутабельных структурах, добавляя ещё типы, выворачивая порядок исполнения, но в итоге код получается сложнее для всяких оптимизаторов, запутаннее для человека, и далёк от спеки, который выдаёт бизнес. С билдерами же всё пишется как слышится и код хоть даже юзерам показывать можно — всё ясно что куда и откуда и в каком порядке. ·>Верно. Так же можно и забыть p = при updateAAA(p). Хуже того, можно ошибиться в copy-paste и получится x = updateAAA(y). С билдерами такой проблемы нет в принципе, т.к. используется одна сущность.
S>>и всё будет работать, то с отдельными свойствами композитных объектов так сделать не получится. Если у меня immutable record Point(int x, int y), то запись вроде p.x += 5 запрещена. ·>И таких проблем с билдером нет. Поля билдера можно использовать как аккумуляторы значений из разных мест кода. Особенно хорошо, когда они коллекции.
S>>И вот для обхода этой проблемы предлагается конструкция with. S>>Она позволяет легко и очевидно перейти от мутабельного кода к иммутабельному: S>>ip = ip with X = 10; ·>Тоже так себе, т.к. "неожиданно" меняется тип выражения: print("new position {}", mp.x += 5);
S>>>>Почему нельзя просто сделать тот же With? Ведь при присванивании "в себя же" ескейп-анализ уберёт всю лишнюю нагрузку на GC, в итоге имеем тот же перформанс, лаконичный синтаксис, и отсутствие необходимости для каждого DTO еще и цельный билдер расписывать. ·>На ea я бы не полагался, ведь в 40 полях, разбито всё по методам-классам, коллекции, етс, это уже всё заблудится и потеряется.
S>>·> p = updateDDD(p); S>>Да, а почему нет? ·>Помимо уже упомянутых недостатков выше, неясно зачем вообще так делать. Ну да, объект типа иммутабельный, а переменная-то внезапно мутабельная... Та же ж, вид с боку.
Начинаю склоняться к тому, что такой подход плохо подходит для "толстых" объектов.
Для маленьких и несложных он подходит гораздо лучше — ну вот та же дата, или какой-нибудь Decimal. Для них иммутабельность абсолютно органична, т.к. если уж мы что меняем, так оно меняет объект "полностью".
Для ваших толстых DTO алгебра несколько другая; тут, действительно, с принудительно-иммутабельными структурами проще напороть, чем без них.
S>>·>как тут можнро сделать красивее? S>>Эмм, да всё что угодно тут будет красивее. S>> (o, i) = updateBBB(o, i); ·>А теперь авто-рефакторингом добавь в updateBBB по всему коду параметр p. ·>Код выглядит красивее, но мейнтейнить его, внезапно, сложнее.
Ну, вот рефакторинг тут ничуть не сложнее. Никакой рефакторинг вам из воздуха параметр не вытащит, поэтому придётся идти и руками в каждом месте прописывать, что мы туда передаём.
Заодно поправим и возвращаемые параметры.
S>>·>Лично мне пока самым удобным показалось то, что генерирует Avro, как тут. (генерацию setters для самих dto можно запретить, оставить только у билдеров). S>>Ну, это тот самый способ со сменой сигнатуры у сеттера на возврат self. Так себе решение, по сравнению с with operator, кмк. Лучше, чем ничего, конечно. ·>Суть в том, что есть два типа — иммутабельный dto и мутабельный билдер. И их можно конвертить между собой и явно использовать в разных частях кода.
Вообще да, в целом выглядит всё же получше, чем цепочка иммутабельных преобразований. Единственное что меня царапает — так это бойлерплейт в билдере. Если автоматизировать порождение билдеров на основе рекорд-класса — то, пожалуй, такой подход будет лучше всего остального.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>·>Помимо уже упомянутых недостатков выше, неясно зачем вообще так делать. Ну да, объект типа иммутабельный, а переменная-то внезапно мутабельная... Та же ж, вид с боку. S>Начинаю склоняться к тому, что такой подход плохо подходит для "толстых" объектов. S>Для маленьких и несложных он подходит гораздо лучше — ну вот та же дата, или какой-нибудь Decimal. Для них иммутабельность абсолютно органична, т.к. если уж мы что меняем, так оно меняет объект "полностью". S>Для ваших толстых DTO алгебра несколько другая; тут, действительно, с принудительно-иммутабельными структурами проще напороть, чем без них.
Вот именно. А у "тонких" объектов и проблемы с конструктором нет, ибо там два-три параметра и вся эта шелуха с withers особо-то и не нужна.
S>>>·>как тут можнро сделать красивее? S>>>Эмм, да всё что угодно тут будет красивее. S>>> (o, i) = updateBBB(o, i); S>·>А теперь авто-рефакторингом добавь в updateBBB по всему коду параметр p. S>·>Код выглядит красивее, но мейнтейнить его, внезапно, сложнее. S>Ну, вот рефакторинг тут ничуть не сложнее. Никакой рефакторинг вам из воздуха параметр не вытащит, поэтому придётся идти и руками в каждом месте прописывать, что мы туда передаём. IDEA уж много лет умеет — пытается вытащить "из воздуха" или оставляет дефолт, если не получается. Те места где не получилось — будут красными и там можно ещё какой-нибудь рефакторинг запустить.
S>Заодно поправим и возвращаемые параметры.
Впрочем, в java нет туплов, а если бы были может IDEA могла бы и возвращаемые значения с Use Any Var рефакторить, хз. Но в любом случае, это уже был бы более сложный двойной рефакторинг — и параметр, и возвращаемое значение одновременно и всё неясно ради чего. Создали лишнюю сложность с иммутабельностью и приходится теперь героически бороться туплами и хитрыми рефакторингами.
S>·>Суть в том, что есть два типа — иммутабельный dto и мутабельный билдер. И их можно конвертить между собой и явно использовать в разных частях кода. S>Вообще да, в целом выглядит всё же получше, чем цепочка иммутабельных преобразований. Единственное что меня царапает — так это бойлерплейт в билдере. Если автоматизировать порождение билдеров на основе рекорд-класса — то, пожалуй, такой подход будет лучше всего остального.
Вот об этом и разговор. Пока, как мне кажется, более вменяемый подход это генерация кода на основе какого-нибудь описания протокола типа avro. Потом есть lombok с @Builder-аннотацией и т.п., но работает оно через хаки. Ещё в той же IDEA есть кнопочка "сгенерить билдер", которая всё сделает, но это один раз, после этого — добавление нового поля требует бОльших усилий.
А что такое придумать, чтобы прям из коробки в ЯП было и было юзабельно и достаточно универсально — не очень ясно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
S>>·>Суть в том, что есть два типа — иммутабельный dto и мутабельный билдер. И их можно конвертить между собой и явно использовать в разных частях кода. S>>Вообще да, в целом выглядит всё же получше, чем цепочка иммутабельных преобразований. Единственное что меня царапает — так это бойлерплейт в билдере. Если автоматизировать порождение билдеров на основе рекорд-класса — то, пожалуй, такой подход будет лучше всего остального. ·>Вот об этом и разговор. Пока, как мне кажется, более вменяемый подход это генерация кода на основе какого-нибудь описания протокола типа avro. Потом есть lombok с @Builder-аннотацией и т.п., но работает оно через хаки. Ещё в той же IDEA есть кнопочка "сгенерить билдер", которая всё сделает, но это один раз, после этого — добавление нового поля требует бОльших усилий. ·>А что такое придумать, чтобы прям из коробки в ЯП было и было юзабельно и достаточно универсально — не очень ясно.
Ну, в дотнете это работает из коробки через Source Generators.
Вон, пару лет назад кто-то уже с этим игрался: https://justsimplycode.com/2020/12/06/auto-generate-builders-using-source-generator-in-net-5/
Наверняка к данному моменту уже есть более-менее отлаженная реализация.
Преимущество в том, что это никакие не хаки, а официально поддерживаемая технология. Интегрирована с интеллисенсом и билд-процессом.
Аннотировал нужный Record-класс атрибутом — и поехали.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>·>А что такое придумать, чтобы прям из коробки в ЯП было и было юзабельно и достаточно универсально — не очень ясно. S>Ну, в дотнете это работает из коробки через Source Generators.
Ох, ты прав. Меня vsb смутил. Можно же аннотацию на рекорды вешать.
S>Вон, пару лет назад кто-то уже с этим игрался: https://justsimplycode.com/2020/12/06/auto-generate-builders-using-source-generator-in-net-5/ S>Наверняка к данному моменту уже есть более-менее отлаженная реализация.
Ага, в java это уже 3 года доступно, а я торможу. Попробую сейчас...
S>Преимущество в том, что это никакие не хаки, а официально поддерживаемая технология. Интегрирована с интеллисенсом и билд-процессом. S>Аннотировал нужный Record-класс атрибутом — и поехали.
Угу, в java то же, притом раньше, чем SG придумали.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, vsb, Вы писали:
vsb>Я сам не пробовал annotation processor-ы писать, поэтому могу ошибаться. Но в моём понимании annotation processor может генерировать либо полностью новый класс, который старый код видеть не будет. Либо какие-то там проверки делать и тд. То бишь максимум, который можно сделать оставаясь в рамках того, что разрешено — руками объявить interface Person { String name(); String name(String name); }, а annotation processor сгенерирует class PersonImpl implements Person { ... } и ты его экземпляр уже как-то получишь.
Вот тут интересное решение, позволяет навешивать генерацию даже на чужие типы. https://github.com/randgalt/record-builder
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, vsb, Вы писали:
vsb>Я не считаю, что .NET идет правильным путем, суя в язык как сорока всё блестящее.
С чего ты взял что это так? Если поинтересоваться процессом, то можно увидеть что реджектится огромное количество пропозалов, за каждым из которых часто стоит толпа людей, которые прям жить без этой фичи не могут.
Здравствуйте, ·, Вы писали:
·>Суть в том, что api обычно уже имеет некое описание независимое от каких-либо ЯП.
Вот только часто такое API живет на стыке двух миров: REST и OOP, RDBMS и OOP, и т.п. У которых разные подходы, порождающие разные оптимальные модели. А вот генераторы модели не меняют и не адаптируют, в результате чего результат генерации сильно хуже оптимального.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>·>Суть в том, что api обычно уже имеет некое описание независимое от каких-либо ЯП. НС>Вот только часто такое API живет на стыке двух миров: REST и OOP, RDBMS и OOP, и т.п. У которых разные подходы, порождающие разные оптимальные модели.
Ну да. Если это не API напрямую подключаемой библиотеки на том же ЯП, то будет некая сериализация.
НС>А вот генераторы модели не меняют и не адаптируют, в результате чего результат генерации сильно хуже оптимального.
Вот это неясно. Генераторы выбирают и оптимизируют под условия. Если это rest и там принято json, то и оптимизировать в принципе нечего, тормозить будет безбожно в любом случае, зато human-friendly. А если нужна скорость, то берут protobuf или даже SBE и там всё до тактов вылизано, cache-friendly и т.п., на случай требований low-latency.
Более того, генераторы, как правило, можно подпиливать, для адаптации кода под условия.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
НС>>А вот генераторы модели не меняют и не адаптируют, в результате чего результат генерации сильно хуже оптимального. ·>Вот это неясно. Генераторы выбирают и оптимизируют под условия. Если это rest и там принято json, то и оптимизировать в принципе нечего, тормозить будет безбожно в любом случае, зато human-friendly. А если нужна скорость, то берут protobuf или даже SBE и там всё до тактов вылизано, cache-friendly и т.п., на случай требований low-latency.
Я писал не про оптимизации, а про оптимальность моделей. Реляционная модель отличается от ОО и напрямую ложится плохо. То же самое касаемо ОО и REST. В лучшем случае генераторы позволяют в полуручном режиме выходные модели допиливать, но чаще даже такой возможности нет, жри кактус.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>·>Вот это неясно. Генераторы выбирают и оптимизируют под условия. Если это rest и там принято json, то и оптимизировать в принципе нечего, тормозить будет безбожно в любом случае, зато human-friendly. А если нужна скорость, то берут protobuf или даже SBE и там всё до тактов вылизано, cache-friendly и т.п., на случай требований low-latency. НС>Я писал не про оптимизации, а про оптимальность моделей. Реляционная модель отличается от ОО и напрямую ложится плохо. То же самое касаемо ОО и REST. В лучшем случае генераторы позволяют в полуручном режиме выходные модели допиливать, но чаще даже такой возможности нет, жри кактус.
Да всякое бывает. По ddl можно генерить код на ЯП, чтобы строить те же sql запросы с type-safety, без всякого ОО. И в случае rest то же — описываем структуру документа и генерим код, чтобы можно было удобно создавать json-документы, без всякого ОО. Какие варианты ещё в случае развесистых rest api? Строки клеить что-ли?
Ты, видимо, об ORM говоришь, неясно причём тут оно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Ночной Смотрящий, Вы писали:
vsb>>Я не считаю, что .NET идет правильным путем, суя в язык как сорока всё блестящее.
НС>С чего ты взял что это так? Если поинтересоваться процессом, то можно увидеть что реджектится огромное количество пропозалов, за каждым из которых часто стоит толпа людей, которые прям жить без этой фичи не могут.
Ну издалека оно так кажется. Когда в жаве, голанге и других языках годами вообще ничего существенного не добавляется, а в .NET каждый релиз куча новых фич.
Здравствуйте, ·, Вы писали:
·>Да всякое бывает. По ddl можно генерить код на ЯП, чтобы строить те же sql запросы с type-safety, без всякого ОО.
Можно. А вот если ты те модели выставишь в публичное API — скорее всего будет кака.
·>Ты, видимо, об ORM говоришь, неясно причём тут оно.
Давай я тебе на простом примере продемонстрирую. Возьмем REST API и метод получения списка сущностей. Для одного типа сущностей мы, как правило, имеем один uri (часто это явно зафиксировано в гайдлайнах), и, как следствие, один метод контроллера сервиса. Если при этом мы имеем несколько разных юзкейсов, то сумма этих юзкейсов порождает набор фильтров в параметрах на все случаи жизни.
Далее из этого автоматически генерируется OpenAPI spec (тут все еще ОК, потому что это все еще модель REST), а потом по ней клиент. И вот тут нам генератор породит и на клиенте метод-всемогутор, который, конечно, обладает максимальной гибкостью, но при этом еще и максимально неудобен, потому что нет ни одного юзкейса где нужны были бы все фильтры. И во вручную написанном клиенте вместо одного метода-всемогутера было бы N методов под каждый юзкейс.
Аналогичная ситуация и с классами-DTO.
Теоретически можно было бы обвесить метод контроллера метой, которая бы описывала юзкейсы, и которая передавалась бы в составе OpenAPI spec, а генератор эту мету доставал бы и генерил удобного клиента, но на практике я такого генератора не видел даже близко.
Аналогичная ситуация и с ORM. Вся идея с тем, что мы ORM кормим POCO, а потом эти же POCO выставляем в публичный API работает только на демках. Еще хуже работает когда эти POCO не вручную пишутся, а генерируются из схемы БД. Поэтому в реальности в лучшем случае только сабсет модели общий для всей цепочки, а полная модель у ORM, REST и клиента у каждого своя.
Это я еще не вспоминаю про те же ad-hoc типы в ORM, которых в Java нет, но там где они есть это очень мощная штука.
Здравствуйте, vsb, Вы писали:
vsb>Ну издалека оно так кажется. Когда в жаве, голанге и других языках годами вообще ничего существенного не добавляется, а в .NET каждый релиз куча новых фич.
Да не особо то и куча. Мелочевка в основном. Но в целом это не вопрос тянуть все блестящее (если ты внимательно посмотришь — все новые фичи это не просто затянутое откуда то, а основательно преломленное через реалии и идеологию шарпа), а вопрос подхода и инвестиций.
Вот где сорочий подход, так это в Скале.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>·>Да всякое бывает. По ddl можно генерить код на ЯП, чтобы строить те же sql запросы с type-safety, без всякого ОО. НС>Можно. А вот если ты те модели выставишь в публичное API — скорее всего будет кака.
Whatever, другое api может иметь другое описание и по нему тоже генериться код.
НС>·>Ты, видимо, об ORM говоришь, неясно причём тут оно. НС>Давай я тебе на простом примере продемонстрирую. Возьмем REST API и метод получения списка сущностей. Для одного типа сущностей мы, как правило, имеем один uri (часто это явно зафиксировано в гайдлайнах), и, как следствие, один метод контроллера сервиса. Если при этом мы имеем несколько разных юзкейсов, то сумма этих юзкейсов порождает набор фильтров в параметрах на все случаи жизни. НС>Далее из этого автоматически генерируется OpenAPI spec (тут все еще ОК, потому что это все еще модель REST), а потом по ней клиент. И вот тут нам генератор породит и на клиенте метод-всемогутор, который, конечно, обладает максимальной гибкостью, но при этом еще и максимально неудобен, потому что нет ни одного юзкейса где нужны были бы все фильтры.
Это довольно частный пример. Впрочем даже в этом случае, можно поверх метода-всемогутера написать соответствующие клиентские юзкейсы конкретного клиента. Сервер предоставляет общий api.
НС>И во вручную написанном клиенте вместо одного метода-всемогутера было бы N методов под каждый юзкейс.
Вручную — это как? Склейкой строк?
НС>Аналогичная ситуация и с классами-DTO.
И аналогичные подходы организации кода.
НС>Теоретически можно было бы обвесить метод контроллера метой, которая бы описывала юзкейсы, и которая передавалась бы в составе OpenAPI spec, а генератор эту мету доставал бы и генерил удобного клиента, но на практике я такого генератора не видел даже близко.
Потому что ты похоже путаешь сценарии конкретного клиента и API предоставляемый сервером, который должен уметь обслуживать различных клиентов.
НС>Аналогичная ситуация и с ORM. Вся идея с тем, что мы ORM кормим POCO, а потом эти же POCO выставляем в публичный API работает только на демках. Еще хуже работает когда эти POCO не вручную пишутся, а генерируются из схемы БД. Поэтому в реальности в лучшем случае только сабсет модели общий для всей цепочки, а полная модель у ORM, REST и клиента у каждого своя.
Это проблема SRP, а не кодогенерации. Если одну и ту же штуку пытаться впихнуть в два места, то ничего хорошего не выйдет.
У тебя есть модель субд, у тебя есть модель клиентов. По обоим моделям генерим код и пишем связывающую логику, используя все преимущества строгой типизации.
НС>Это я еще не вспоминаю про те же ad-hoc типы в ORM, которых в Java нет, но там где они есть это очень мощная штука.
Много лет уж как есть, с появления var можно и переменные присваивать, примерно так:
var thing = Stream.of(1, 2, 3)
.map(i -> new Object() {final int twice = i * 2; final String str = "val" + i;})
.filter(v -> v.twice > 3 && v.str.contains("3"))
.findFirst()
.orElseThrow();
System.out.println(thing.str);
Немного многословно, приходится типы полей прописывать, но вполне юзабельно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Вот где сорочий подход, так это в Скале.
На скалу не смотрел, но в дотнете реально много что прямо таки достаёт.
Когда пишешь что-то типа from p in ... where p.Age > 42 select (p.Age, p.Name), а тебе в ответ "CS8143: An expression tree may not contain a tuple literal".
И там уже даже вроде бы решение придумали, которое всех бы устроило, но "this proposal isn't currently Championed which means that it's not being worked on by the team at this time."
Уйдемте отсюда, Румата! У вас слишком богатые погреба.