Информация об изменениях

Сообщение Re[7]: Опциональные типы от 25.02.2017 14:12

Изменено 25.02.2017 14:31 meadow_meal

Re[7]: Опциональные типы
Здравствуйте, netch80, Вы писали:

V>>>
V>>>enum ClanUpdate { NoUpdate, EnterClan, ExitClan };

V>>>union ClanEvent switch (ClanUpdate) { 
V>>>       case NoUpdate: ; 
V>>>       case EnterClan: int ClanId; 
V>>>       case ExitClan: ; 
V>>>}; 
V>>>


N>Ну если ты этому человеку опишешь это (в Erlang стиле) как {ok, ClanId:integer()} | no_update | exited — будет совершенно очевидно.


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

Например, как у вас принято обозначать опциональные поля в Эрланге — T | undefined, или {ok, T} | undefined, или что-то еще. В OTP на эту тему зоопарк, но в собственном фрэеймворке и проектах логично выбрать что-то одно. Мы выбрали T | undefined, это хорошо подходит, если часто использовать рекорды (а мы их любим, т.к. любим intellisense и статические проверки) и проплисты. Ну и синтаксического шума меньше. Еще одна причина — {ok, T} удобно работает именно с PM, чтобы свалиться по месту, но на практике именно в нашем случае мы редко хотим свалиться с badmatch, т.к. отсутствие опционального поля в нашем случае это чаще всего throw — ошибка валидации или рассинхрона, в отличие от error — признака бага, и мы их по-разному обрабатываем и смешивать не хотим. Если же использовать case, то там и без разницы, с тэгом или без.

Итак, T | undefined. Значит, где-то есть функция clan(state()) -> clan_id() | undefined, а значит есть и парная функция для записи этого значения — set_clan(state(), clan_id() | undefined) -> state(). И вот это то место, где удобно уведомлять подписчиков.

Итак, в реализации set_clan мы должны вызвать код уведомления подписчиков. Он может выглядеть так:
set_clan(State, Clan) ->
... = unit_update:set(State, clan, Clan)

или так:
set_clan(State, Clan) ->
... = unit_update:set(State, clan, case Clan of undefined -> exited; _ -> Clan end).
или другие варианты с PM — это не так важно.

Это все: другого релевантного кода мы руками не пишем (да даже и здесь часто будет parse_transform, а перечисленные функции сгенерированы, но если сгенерировать первый вариант тривиально, то второй — нет). Напрямую с вложенным Optional мы в серверном коде не сталкиваемся. Нутрянка unit_update (сгенерированного) использует map, и отсутствие поля в мапе трактует как no_update (или None верхнего уровня) — но пользователю это и не важно.

Важно еще и то, что этот код будет совершенно идентичный и для required полей.

И объяснить новому человеку принципы обновления полей элементарно — для любого неколлекционного T просто добавь T? в update-рекорд — и все.

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

Я не жду, что ты обязательно согласишься, но надеюсь что хоть причины подобного решения мне удалось объяснить.

N>А вот заворот в несколько слоёв Optional<> хуже и тем, что после второго уровня шарики забегают за ролики, и тем, что туда не вложить никакую дополнительную информацию.


Опять же, если говорить в общем, без конкретики, то я согласен.

N>Не хочу тут рассуждать о формате IDL, всё это вторично, а то, что описал тут — важнее. IMHO.


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

Да, теоретически возможен более компактный синтаксис IDL для tagged unions (сейчас мы его не поддерживаем), и если бы использование optional в данном случае приводило к проблемам, я бы думал в эту сторону. А так — нет.
Re[7]: Опциональные типы
Здравствуйте, netch80, Вы писали:

V>>>
V>>>enum ClanUpdate { NoUpdate, EnterClan, ExitClan };

V>>>union ClanEvent switch (ClanUpdate) { 
V>>>       case NoUpdate: ; 
V>>>       case EnterClan: int ClanId; 
V>>>       case ExitClan: ; 
V>>>}; 
V>>>


N>Ну если ты этому человеку опишешь это (в Erlang стиле) как {ok, ClanId:integer()} | no_update | exited — будет совершенно очевидно.


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

(Отредактировано:
До меня запоздало дошло, что если заменить T | undefined на {ok, T} | undefined, то в аргументации ничего не изменится, поэтому следующий абзац можно пропустить, а дальше подставлять любой из вариантов по вкусу)

Например, как у вас принято обозначать опциональные поля в Эрланге — T | undefined, или {ok, T} | undefined, или что-то еще. В OTP на эту тему зоопарк, но в собственном фрэеймворке и проектах логично выбрать что-то одно. Мы выбрали T | undefined, это хорошо подходит, если часто использовать рекорды (а мы их любим, т.к. любим intellisense и статические проверки) и проплисты. Ну и синтаксического шума меньше. Еще одна причина — {ok, T} удобно работает именно с PM, чтобы свалиться по месту, но на практике именно в нашем случае мы редко хотим свалиться с badmatch, т.к. отсутствие опционального поля в нашем случае это чаще всего throw — ошибка валидации или рассинхрона, в отличие от error — признака бага, и мы их по-разному обрабатываем и смешивать не хотим. Если же использовать case, то там и без разницы, с тэгом или без.

Итак, T | undefined. Значит, где-то есть функция clan(state()) -> clan_id() | undefined, а значит есть и парная функция для записи этого значения — set_clan(state(), clan_id() | undefined) -> state(). И вот это то место, где удобно уведомлять подписчиков.

Итак, в реализации set_clan мы должны вызвать код уведомления подписчиков. Он может выглядеть так:
set_clan(State, Clan) ->
... = unit_update:set(State, clan, Clan)

или так:
set_clan(State, Clan) ->
... = unit_update:set(State, clan, case Clan of undefined -> exited; _ -> Clan end).
или другие варианты с PM — это не так важно.

Это все: другого релевантного кода мы руками не пишем (да даже и здесь часто будет parse_transform, а перечисленные функции сгенерированы, но если сгенерировать первый вариант тривиально, то второй — нет). Напрямую с вложенным Optional мы в серверном коде не сталкиваемся. Нутрянка unit_update (сгенерированного) использует map, и отсутствие поля в мапе трактует как no_update (или None верхнего уровня) — но пользователю это и не важно.

Важно еще и то, что этот код будет совершенно идентичный и для required полей.

И объяснить новому человеку принципы обновления полей элементарно — для любого неколлекционного T просто добавь T? в update-рекорд — и все.

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

Я не жду, что ты обязательно согласишься, но надеюсь что хоть причины подобного решения мне удалось объяснить.

N>А вот заворот в несколько слоёв Optional<> хуже и тем, что после второго уровня шарики забегают за ролики, и тем, что туда не вложить никакую дополнительную информацию.


Опять же, если говорить в общем, без конкретики, то я согласен.

N>Не хочу тут рассуждать о формате IDL, всё это вторично, а то, что описал тут — важнее. IMHO.


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

Да, теоретически возможен более компактный синтаксис IDL для tagged unions (сейчас мы его не поддерживаем), и если бы использование optional в данном случае приводило к проблемам, я бы думал в эту сторону. А так — нет.