G>>public Order[*] read_order_from_db(OrderId id); // читаем уже правильный ордер, либо Order[New] либо Order[Processed]
G>>
G>А, пардон, я неправильно понял, что ты имеешь ввиду. Да, в таком случае придется извернуться и после чтения из базы прогнать ордер по состояниям снова. G>Но все тоже возможно.
M>public Order[*] read_order_from_db(OrderId id);
M>// каким образом тут компилятор проверит, что за Order сюда идет?
M>// заказ же из базы читается
M>change_order_amount(Order, Amount)
M>
см. выше. придется догнать до нужного состояния снова. тут часть проверок действительно ляжет на рантайм.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[29]: Haskell нужен! (в Standard Chartered Bank)
Здравствуйте, Mamut, Вы писали:
M>Зачем извращаться, и что это даст?
ну вот мы и вернулись к тому с чего начали это даст возможность покрыть часть ошибок в компайл-тайм. В случае с чтением из базы, часть рантайм проверок отработает и мы снова имеем правильный тип и снова полагаемся на компилятор.
Какой процент ошибок так удастся поймать и стоит ли овчинка выделки видимо зависит от проекта.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[29]: Haskell нужен! (в Standard Chartered Bank)
:D
G>это даст возможность покрыть часть ошибок в компайл-тайм. В случае с чтением из базы, часть рантайм проверок отработает и мы снова имеем правильный тип и снова полагаемся на компилятор.
Но ведь фишка в том, что практически все — это чтение из какого-либо источника данных
G>Какой процент ошибок так удастся поймать и стоит ли овчинка выделки видимо зависит от проекта.
Как я уже говорил, есть далеко не одно исследование, которое говорит, что таких ошибок достаточно мало
Сейчас, естетсвенно, лень эти исследования искать (а гугл вообще двинулся мозгами и сейчас в нем невозможно что-либо найти)
Здравствуйте, Mamut, Вы писали:
M>Это не ошибка в логике. Мы передаем черный ящик в API, который знает, как с этим ящиком работать. Где ошибка логики — неизвестно
Ошибка в логике заключается в том, что мы вызываем (пытаемся) функцию change_order_amount, передавая ей Order, у которого неизвестно состояние.
Ошибка именно в нашем коде, потому что API явно декларирует требования к заказу.
ARK>>Согласно спецификации, change_order_amount обязан оперировать не с любыми Order, а только с их подмножеством, у которых Sent=true. Мы записываем в сигнатуру change_order_amount тип "UnsentOrder" (вместо любого Order) — и получаем в каких-то местах ошибку компиляции (вот в этих двух строчках в частности). ARK>>И это правильно, потому что этот код некорректен. Чтобы он стал корректным, добавляем: ARK>>
ARK>> Order = read_order_from_db(OrderId);
ARK>> if (Order.Sent = true)
ARK>> change_order_amount(Order, Amount);
ARK>>
ARK>>и внезапно код теперь компилируется.
M>Во-первых, стоп. Выше ничего не изменилось. Тип Order как был, так и остался Order. Или оборачивание его в if(Order.Sent = true) автоматом превратило его в SentOrder?
Да, Order стал SentOrder после проверки. SentOrder — это такой "виртуальный" тип, который задается наложением ограничения на другой тип.
Пример такого подхода можно увидеть здесь: http://ceylon-lang.org/documentation/1.0/tour/types/
Проверка переменной в if или match "сужает" ее тип.
M>Во-вторых, ровно ноль разницы с API, которое имеет под капотом if Order.Sent/if Order.NotSent Более того, даже объемы тестов будут одинаковые. Причем при росте количества условий сам тип будет становиться мешаниной, а вызовы чего бы то ни было с этим типом будут ничем не отличаться от вызвовов без типов
Не-не, в API будет этот if в нескольких местах (а ведь он может быть и не таким простым). Мы можем заменить ифы просто нужным типом.
Соответственно, мы один раз сделали все необходимые проверки, а дальше у нас просто переменная нужного типа путешествует по системе, а компилятор следит, чтобы мы ее не засунули туда, куда не нужно.
Понятно, что в каких-то сценариях количество проверок будет одинаковое. А в каких-то одна проверка на входе и дальше 10 вызовов без проверок.
Насчет мешанины в типе — да, наверно это может быть. ИМХО, это значит, что мы где-то что-то сделали не так (засунули в тип слишком много ответственностей).
ARK>>У нас тут речь как раз о том, чтобы закладывать в программу больше семантики на уровне типов, тогда и дополнительных проверок будет больше.
M>Пока что я не убежден, от слова совсем. Разницы с API пока что ноль. Плюс проверки размазываются по всему коду: в одном месте что-то форсируется на уровне типов, в другом месте надо написать сто if'ов, чтобы впихнуть полученный объект в вызов.
Ну, тут плюс в том, что компилятор не даст сделать вызов, если нужных проверок не будет (т.е. ограничения на уровне типов влияют на обычный код) — это и есть та самая дополнительная корректность.
Другой вопрос, что все на типах запрограммировать все равно не удастся (на современном уровне технологий), поэтому будут и обычные проверки в коде тоже.
Re[25]: Haskell нужен! (в Standard Chartered Bank)
ARK>Да, Order стал SentOrder после проверки. SentOrder — это такой "виртуальный" тип, который задается наложением ограничения на другой тип. ARK>Пример такого подхода можно увидеть здесь: http://ceylon-lang.org/documentation/1.0/tour/types/ ARK>Проверка переменной в if или match "сужает" ее тип.
По ссылке не вижу ничего подобного. Более того, я вижу там банальное if typeof(Object) == что-то там Только присыпанное синтаксическим сахаром
M>>Во-вторых, ровно ноль разницы с API, которое имеет под капотом if Order.Sent/if Order.NotSent Более того, даже объемы тестов будут одинаковые. Причем при росте количества условий сам тип будет становиться мешаниной, а вызовы чего бы то ни было с этим типом будут ничем не отличаться от вызвовов без типов
ARK>Не-не, в API будет этот if в нескольких местах (а ведь он может быть и не таким простым).
Именно. if в нескольких местах, да еще не такой простой. Нафига козе баян?
ARK>Мы можем заменить ифы просто нужным типом.
Чтоа?
ARK>Соответственно, мы один раз сделали все необходимые проверки, а дальше у нас просто переменная нужного типа путешествует по системе, а компилятор следит, чтобы мы ее не засунули туда, куда не нужно.
Да-да. Следит, «нужного типа». Только вот незадача-то. Объект у нас одного типа — заказ.
И дальше над ним может идти очень длинная последовательность действий. Где в одном действии ему надо быть уже отосланным, в другом — неотосланным, в пятом — отосланным, но неоплаченным, в десятом — просто не иметь статуса "fraud". Как предлагаешь поступать? Писать стопятьсот типов на каждый чих и комбинацию, а потом стопятьсот if'ов, инициализаторов и кастований из одного в другое?
ARK>Понятно, что в каких-то сценариях количество проверок будет одинаковое. А в каких-то одна проверка на входе и дальше 10 вызовов без проверок.
Это, вообще-то, КО программирования. Валидность объекта проверяет первая функция, а дальше ненужных проверок делать не нужно.
ARK>Насчет мешанины в типе — да, наверно это может быть. ИМХО, это значит, что мы где-то что-то сделали не так (засунули в тип слишком много ответственностей).
Да нет никаких много ответсвенностей Просто заказ. Только заказ — это простая сущность только в синтетических примерах. На практике — это достаточно сложная сущность, от которой разным компонентам системы нужно очень много разных вещей.
ARK>>>У нас тут речь как раз о том, чтобы закладывать в программу больше семантики на уровне типов, тогда и дополнительных проверок будет больше.
M>>Пока что я не убежден, от слова совсем. Разницы с API пока что ноль. Плюс проверки размазываются по всему коду: в одном месте что-то форсируется на уровне типов, в другом месте надо написать сто if'ов, чтобы впихнуть полученный объект в вызов.
ARK>Ну, тут плюс в том, что компилятор не даст сделать вызов, если нужных проверок не будет (т.е. ограничения на уровне типов влияют на обычный код) — это и есть та самая дополнительная корректность.
Которая на практике выстреливает раз в год Не говоря уже о том, что всю эту логику, навороченную в типе еще тоже надо проверить каким-то образом.
ARK>Другой вопрос, что все на типах запрограммировать все равно не удастся (на современном уровне технологий), поэтому будут и обычные проверки в коде тоже.
Тогда нафига козе баян? Ну и как это? Меня тут активно пытались убедить, что все прекрасно, и чуть ли ен все можно в типы запихнуть
Здравствуйте, Mamut, Вы писали:
ARK>>Да, Order стал SentOrder после проверки. SentOrder — это такой "виртуальный" тип, который задается наложением ограничения на другой тип. ARK>>Пример такого подхода можно увидеть здесь: http://ceylon-lang.org/documentation/1.0/tour/types/ ARK>>Проверка переменной в if или match "сужает" ее тип.
M>По ссылке не вижу ничего подобного. Более того, я вижу там банальное if typeof(Object) == что-то там Только присыпанное синтаксическим сахаром
Тут "obj" внутри if стал уже другого типа (Printable, а извне if он просто Object) — причем без всяких кастов.
ARK>>Мы можем заменить ифы просто нужным типом. M>Чтоа?
Я имею в виду что-то типа:
void Func1(MyObj obj)
{
if (obj.Prop == 3 && .... // тут много проверок
Use(obj);
}
void Func2(MyObj obj)
{
if (obj.Prop == 3 && obj.Prop2 == 4 && .... // тут те же проверки, и еще пара других
Use(obj);
}
MyObj obj = GetMyObj();
Func1(obj);
Func2(obj);
// предлагаемый вариант (псевдокод)type MyCoolObj = MyObj where obj.Prop == 3 && ... // тут все проверкиvoid Func1(MyCoolObj obj)
{
Use(obj); // проверок нет
}
void Func2(MyCoolObj obj)
{
if (obj.Prop2 == 4 && .... // тут только пара "лишних" проверок
Use(obj);
}
MyObj obj = GetMyObj();
if (obj is MyCoolObj) // все проверки в типе, тут ничего нет
{
Func1(obj);
Func2(obj);
}
Отличие второго варианта (проверки в типе + условие "if (obj is MyCoolObj)") от банальной проверки всего, чего нужно, сразу после "MyObj obj = GetMyObj();" — в том, что мы кодируем проверки в типе и дальше можем сразу, подставляя MyCoolObj, гарантировать, что в этом месте все проверки уже сделаны (компилятор проследил). Если мы будем работать с просто MyObj, то контроля со стороны компилятора не будет (хотя работать все будет и так, просто тестировать придется и этот момент тоже).
ARK>>Соответственно, мы один раз сделали все необходимые проверки, а дальше у нас просто переменная нужного типа путешествует по системе, а компилятор следит, чтобы мы ее не засунули туда, куда не нужно. M>Да-да. Следит, «нужного типа». Только вот незадача-то. Объект у нас одного типа — заказ.
Не только, у нас есть просто заказ, есть отосланный заказ и неотосланный заказ. Это как предок и два потомка в ООП, только конверсия производится неявно — просто проверкой условий типа в if или match.
M>И дальше над ним может идти очень длинная последовательность действий. Где в одном действии ему надо быть уже отосланным, в другом — неотосланным, в пятом — отосланным, но неоплаченным, в десятом — просто не иметь статуса "fraud". Как предлагаешь поступать? Писать стопятьсот типов на каждый чих и комбинацию, а потом стопятьсот if'ов, инициализаторов и кастований из одного в другое?
Ну да, звучит, конечно, не очень приятно. В таких местах просто сложная логика и, конечно, сложность магически никуда не испаряется. Тут надо искать компромисс.
ARK>>Понятно, что в каких-то сценариях количество проверок будет одинаковое. А в каких-то одна проверка на входе и дальше 10 вызовов без проверок. M>Это, вообще-то, КО программирования. Валидность объекта проверяет первая функция, а дальше ненужных проверок делать не нужно.
КО, да не совсем. Конечно, ситуация известная, у того же Макконнелла хорошо описана. Но что забавно, у того же Макконнелла описана любопытнейшая ситуация:
В этих обстоятельствах одна и та же ошибка может быть проверена и с помощью утверждения, и обработчиком ошибок. Так, в исходном коде Microsoft Word условия, которые должны быть истинными, сперва помещаются в утверждения, а затем и в коде обработки ошибок рассматривается ситуация, когда утверждение ложно. В столь сложных и долгоживущих приложениях, как Word, утверждения служат для выявления как можно большего числа ошибок периода разработки. Но поскольку приложение очень сложное (миллионы строк кода) и прошло через столько изменений, неразумно ожидать обнаружения и исправления всех мыслимых ошибок до начала поставки приложения пользователям. Поэтому ошибки должны обрабатываться и в промышленной версии системы.
Это в точности та проблема, которую могут смягчить типы. Поскольку язык С++ не позволяет кодировать в типах дополнительную информацию, в больших программах ставят как можно больше проверок, из страха, что кто-то где-то уберет лишнюю проверку и все обрушится. Потому что компилятор бессилен — с его точки зрения все корректно. Если же ЯП позволяет расширить типы дополнительной информацией, то на помощь приходит компилятор.
ARK>>Ну, тут плюс в том, что компилятор не даст сделать вызов, если нужных проверок не будет (т.е. ограничения на уровне типов влияют на обычный код) — это и есть та самая дополнительная корректность. M>Которая на практике выстреливает раз в год Не говоря уже о том, что всю эту логику, навороченную в типе еще тоже надо проверить каким-то образом.
Ну вон пример Макконнелла выше говорит, что иногда такие штуки полезны.
А вот логику в типе проверять нужно, безусловно.
ARK>>Другой вопрос, что все на типах запрограммировать все равно не удастся (на современном уровне технологий), поэтому будут и обычные проверки в коде тоже. M>Тогда нафига козе баян? Ну и как это? Меня тут активно пытались убедить, что все прекрасно, и чуть ли ен все можно в типы запихнуть
Не, ну это явное преувеличение. Во всяком случае, я не знаю языков, которые позволяют _удобно_ записывать такие проверки.
Re[27]: Haskell нужен! (в Standard Chartered Bank)
ARK>Если мы будем работать с просто MyObj, то контроля со стороны компилятора не будет (хотя работать все будет и так, просто тестировать придется и этот момент тоже).
Ну, тестирование все равно будет. Нам же надо будет проверить, что логика в этих типах закодирована правильно И что-то я сомневаюсь, что объем тестирования будет сильно отличаться
ARK>Это в точности та проблема, которую могут смягчить типы. Поскольку язык С++ не позволяет кодировать в типах дополнительную информацию, в больших программах ставят как можно больше проверок, из страха, что кто-то где-то уберет лишнюю проверку и все обрушится. Потому что компилятор бессилен — с его точки зрения все корректно. Если же ЯП позволяет расширить типы дополнительной информацией, то на помощь приходит компилятор.
Но, как мы только что выяснили, это справедливо только для простых объектов в простых сценариях использования
M>public Order[*] read_order_from_db(OrderId id);
M>// каким образом тут компилятор проверит, что за Order сюда идет?
M>// заказ же из базы читается
M>change_order_amount(Order, Amount)
M>
read_order_from_db :: OrderId -> Either NewOrder ProcessedOrder
И теперь для того, чтобы вызвать change_order_amount, требуется паттерн-матчинг для определения "подтипа".
Re[29]: Haskell нужен! (в Standard Chartered Bank)
Только вот незадача-то. Объект у нас одного типа — заказ.
И дальше над ним может идти очень длинная последовательность действий. Где в одном действии ему надо быть уже отосланным, в другом — неотосланным, в пятом — отосланным, но неоплаченным, в десятом — просто не иметь статуса "fraud". Как предлагаешь поступать? Писать стопятьсот типов на каждый чих и комбинацию, а потом стопятьсот if'ов, инициализаторов и кастований из одного в другое?
Почему не помогает? Он же не дает передать в change_order_amount неопределенный Order.
M>Потому что дальше все становится хуже, процитирую отсюда http://rsdn.ru/forum/philosophy/5944219
M>Только вот незадача-то. Объект у нас одного типа — заказ.
M>И дальше над ним может идти очень длинная последовательность действий. Где в одном действии ему надо быть уже отосланным, в другом — неотосланным, в пятом — отосланным, но неоплаченным, в десятом — просто не иметь статуса "fraud". Как предлагаешь поступать? Писать стопятьсот типов на каждый чих и комбинацию, а потом стопятьсот if'ов, инициализаторов и кастований из одного в другое?
M>)
Думаю, что выражать через типы имеет смысл только достаточно стабильную часть логики, т.к. изменение типов может быть болезненной процедурой и затрагивать кучу мест в проекте.
Ограничение "нельзя менять amount у processed order" выглядит стабильным.
Если amount меняется только в одном месте, то, наверно, смысла выражать это правило через типы нет.
Если таких мест 1024, то смысл появляется.
+= Еще один момент: некая функция принимает параметром Order, вызывает другую, та третью и далее по цепочке. Нам нужна гарантия, что эта некая функция не меняет amount, т.к. мы ее вызываем с processed order. Если тип параметра явно указать как ProcessedOrder, то такую гарантию нам даст компилятор, если нет — то остается только просматривать всю цепочку вызовов. Так что вышеуказанные "1024 мест" включают и такие случаи.
Здравствуйте, Mamut, Вы писали:
M>и чем больше у нас условий, тем больше проверок будет в рантайме с предсказуемым вопросом: а нафига козе баян?
Вроде как очевидно — компилятор не даст забыть проверить все необходимые условия при вызове. А при изменении логики ещё и потыкает во все места, где не сможет самостоятельно доказать выполнение необходимых условий. Как по мне, так это очень крутая фича, хотя, конечно, нужно пробовать в реальных условиях. Наверняка не обойдётся без подводных камней, которые существенно попортят всю малину.
Mumitroller.
... << RSDN@Home 1.2.0 alpha 5 rev. 56>>
Re[24]: Haskell нужен! (в Standard Chartered Bank)
Здравствуйте, Mamut, Вы писали:
DM>>В итоге компилятор хотя бы эту логику проверит статически
M>Каким образом, если заказ читается из базы данных? Подробнее в этом обсуждении: http://rsdn.ru/forum/philosophy/5943812
А какая разница, БД там, файл на магнитном барабане или сигнальные костры? Это лишь деталь реализации модуля хранения данных.
Просто типы при записи в базу не должны теряться. Ты же не путаешь заказы с покупателями, хотя и те и другие хранятся в базе? Это разные типы. Вот и здесь тоже, заказы в разных состояниях будут разными типами. У них наверняка и набор полей может быть разный в зависимости от состояния. Скажем, дата отсылки имеет смысл для отосланного заказа, а способ оплаты — для оплаченного.
DM>>и не даст менять сумму когда не положено. Остальные правила проще описать данными.
M>Что значит «проще описать данными»?
Табличка с правилами вычисления максимального изменения суммы в зависимости от страны, например.
Re[31]: Haskell нужен! (в Standard Chartered Bank)
A>Думаю, что выражать через типы имеет смысл только достаточно стабильную часть логики, т.к. изменение типов может быть болезненной процедурой и затрагивать кучу мест в проекте.
Ой, как это? Мне тут рядом говорили, что придется менять не в 1024 местах, а только в одном месте
A>Ограничение "нельзя менять amount у processed order" выглядит стабильным.
Увы, нет Начиная с "все спецификации ad-hoc" из оригинального сообщения и до последнего тикета, который таки позволяет увеличивать сумму заказа для pre-paid закаов согласно конфигурации магазина. И это связано с дополнительной логикой — что, если
A>+= Еще один момент: некая функция принимает параметром Order, вызывает другую, та третью и далее по цепочке. Нам нужна гарантия, что эта некая функция не меняет amount, т.к. мы ее вызываем с processed order. Если тип параметра явно указать как ProcessedOrder, то такую гарантию нам даст компилятор, если нет — то остается только просматривать всю цепочку вызовов. Так что вышеуказанные "1024 мест" включают и такие случаи.
Во-первых, совсем не факт, что нам нужна эта гарантия. Во-вторых, если Order — это черный ящик, и смена суммы происходит через order:set_amount, то внезапно все проверки тоже оказываются в одном месте В-третьих, увеличение суммы может зависить не только от того, запроцессился заказ или нет.
Лишь некоторые из условий:
— заказ не обработан, заказ не pre-paid, сумма повышена, risk chek пройден: повысили
— заказ не обработан, заказ не pre-paid, сумма повышена, risk chek не пройден: не повысили
— заказ обработан, заказ не pre-paid, сумма повышена, risk chek пройден: повысили
— заказ обработан, заказ не pre-paid, сумма повышена, risk chek не пройден: не повысили
...
...
...
и ко всему этому еще надо добавить проверку «а если order expired?», «а надо ли делать re-auth в банке?», «а надо ли...» ну и т.п. :D
DM>Вот и здесь тоже, заказы в разных состояниях будут разными типами. У них наверняка и набор полей может быть разный в зависимости от состояния. Скажем, дата отсылки имеет смысл для отосланного заказа, а способ оплаты — для оплаченного.
/o\
Действительно. Давай сделаем тип UnprocessedNonPrePaidRiskClearedOrder, ProcessedNonPrePaidRiskClearedOrder, UnprocessedPrePaidRiskClearedOrder, ProcessedPrePaidRiskClearedOrder и еще два десякта таких же. Это же так удобно!
DM>>>и не даст менять сумму когда не положено. Остальные правила проще описать данными.
M>>Что значит «проще описать данными»? DM>Табличка с правилами вычисления максимального изменения суммы в зависимости от страны, например.
Она есть. В конфигурации. В базе данных. Зависит от от страны и магазина
Здравствуйте, jazzer, Вы писали:
J>К вопросу о том, что хаскель не нужен и это инструмент для гиков-энтузиастов и никто его в продакшене в здравом уме использовать не будет
Здравствуйте, Mamut, Вы писали:
DM>>Вот и здесь тоже, заказы в разных состояниях будут разными типами. У них наверняка и набор полей может быть разный в зависимости от состояния. Скажем, дата отсылки имеет смысл для отосланного заказа, а способ оплаты — для оплаченного.
M>Действительно. Давай сделаем тип UnprocessedNonPrePaidRiskClearedOrder, ProcessedNonPrePaidRiskClearedOrder, UnprocessedPrePaidRiskClearedOrder, ProcessedPrePaidRiskClearedOrder и еще два десякта таких же. Это же так удобно!
Ну, если ты пишешь на Си или Го, то такая беда, да. В нормальных же языках полиморфизм позволит общие вещи описать лишь однажды, а разницу вынести, так что ненужного повторения много не будет.
Re[18]: Haskell нужен! (в Standard Chartered Bank)
M>Тут не нужны ни нетипизированная программа, ни тестовые данные. Потому что она в лоб решается так, как там описано:
Можно, например, определить функции проверки, обновлений, записывания не на одном и том же типе Order, а на разных типах различных стадий обработки Order.
Тогда если пропустить проверку, перепутать местами этапы обработки, не обновить, не записать — будет ошибка компиляции.
K>>Т.е. ваш вопрос был о том, что будет если вы "забудете про 4-й пункт" на этапе спецификации? M>Вопрос был. Как ты не смог его понять — это выше моего понимания.
Это не ответ на мой вопрос.
M>Да не показано нигде. Идут сплошные сказки и рассказы про то, как все будет зашибись, и я в это должен верить Как магическая система типов все сома отловит, как невозможно будет написать неправильный код и т.п.
Вы что-то сильно разбушевались, как я посмотрю. Напрыгиваете тут на своих оппонентов, как будто мы у вас мясо из борща на коммунальной кухне украли. Хотя, что характерно, никто мясо из борща не крал, сказки не рассказывал и не обещал, что "все будет зашибись". Вы бы воды выпили или там подышали поглубже что ли и успокоились. Как вам такое предложение?
M>Извини, не верю. Учитывая, что ты даже не смог ни разу ответить на простой вопрос «как это все будет проверяться и дебажиться».
Если вы игнорируете ответ, это еще не значит, что вам никто не ответил.
M>[много текста про то, как магический тайпчек все отловит, невзирая на леность программиста, просто поскипано]
Ничего магического в тайпчеке нет, не выдумывайте.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll