Здравствуйте, Poopy Joe, Вы писали:
PJ>Здравствуйте, Sinclair, Вы писали:
PJ>>>Они зависимые, даже если их описывать на ассемблере. Нельзя получить любой из них по своему выбору, можно только вывести один из другого, причем есть всего два пути.
S>>Это понятно. Просто на ассемблере придётся писать руками вообще всё, на шарпе — чуть меньше, а F#, как я понимаю, обходится без бойлерплейта.
PJ>Не в этом дело. Они зависимые потому, что не всякий тип создает ветвление. Если есть трансформации a -> b -> c -> d, то путь ровно один, хотя типов 4 штуки.
PJ>>>Вообще никакой разницы. Возможет только для PaidOrder
S>>Я не понимаю что такое "вообще никакой разницы". Вы только что мне привели в пример простыню кода, которая всё ещё ничего не делает, кроме как описывает статические бизнес-правила.
S>>Не умаляю полезности этого, но по соотношению "логика на строчку кода" этот пока не выигрывает у любого другого варианта.
PJ>Никакой разницы потому, что функция refund принимает только параметр PaidOrder, что логично не так ли?
PJ>Если валидность PaidOrder гарантирована, то это все что надо знать refund. Оно по-определению, в этом случае, не зависит от остальных компонентов системы.
PJ>Что, в конечном итоге, уменьшает размер кода и делает его более надежным.
PJ>Если есть валидные a, b и c, и корректные a -> b, b -> c, то функция a -> b -> c будет тоже корректна. А компилятор не даст тебе собрать d -> c.
S>>У меня и до этого был один Delivered.
PJ>Ну т.е. типы не расползаются. Начал с одного и закончил одним. Какая же это экспонента?
Кроме Delivered у нас ещё много промежуточных типов.
PJ>Что значит склеил? ShippableOrder их ко-произведение. Я использую логику процесса, как я его понимаю. На мой взгляд готовность к отправке не зависит от статуса оплаты, по твоим объяснениям.
Да, не зависит. Но мне непонятно, как вы собираетесь получать из ShippableOrder при помощи одной операции два разных типа — UnpaidShippedOrder и PaidShippedOrder.
PJ>А зачем бы мне это делать? Paid/Unpaid содержит разную информацию, поэтому типы разные. И дает две дополнительные функции refund : PaidOrder -> Result<> и requestPayment : PaidOrder -> Result<>
PJ>Зачем нужен второй prepareToShip я не представляю, но вполне допускаю, что смысл в этом может быть. Но, в этом случае, хоть с типами, хоть без, у тебя ровно два решения: две функции или две ветки if-else. А как еще?
Ну, очень просто. В плюсах это была бы частичная специализация; в C#/Java у нас была бы простая проверка предусловия. Причём, возможно, вообще не выраженного в коде приложения — была бы конфигурация предиката, которую может править администратор вообще без единого запуска компилятора. Кто ж может себе позволить ре-компиляцию и ре-деплоймент каждый раз, как надо изменить одно из правил
PJ>>>И, второй вопрос, как ты это обычно выражешь по-своему?
S>>Обычно это выражается ровно в тех правилах, которые описывают переход состояний. В том методе, который пытается изменить статус отгрузки, проверяется наличие пре-реквизитов отгрузки. И ему всё равно, сколько ещё есть признаков и какие там у них значения. В терминах типов, этот метод полиморфен — он принимает и PaidShippableOrder, и UnpaidShippableOrder, и любой другой ShippableOrder.
PJ>Ты сам говорил про важность кода. Вот тут как раз такой случай.
public static void ShipOrder(Order o)
{
Assert(ShippingManager.IsShippingAddressValid(o.ShipmentMethod, o.ShippingAddress);
Assert(BillingManager.IsOrderOkForDelivery(o.BillingMethod, o.Customer);
Assert(o.ProcessingStage == ProcessingStage.ReadyForPickup);
var pickupSchedule = ShippingManager.SchedulePickup(o);
OrderManager.SetStage(ProcessingStage.PickupScheduled, pickupSchedule);
}
PJ>Реально заначимый код это sunny day path. Все остальное это бойлерплейт. Удачи с таким кодом.
По моему опыту, sunny day path — это 7% кода. Как раз он и есть boilerplate. Всё остальное — это детальные политики того, что делать, когда всё пойдёт не так.
Фрод скрининг, пересортица, обработка отказов сети, обработка отказов от партнёров, вот это вот всё. Потому что happy path — в основном один, или два, а способов сломаться — много.
и C# всё отлично откомпилирует, а F# бы дал по рукам?
PJ>Смысл моей фразы в том, что все описанное на f# можно сделать и на c#. Я вовсе не призываю тебя писать на c#, если есть возможность писать на f#, это было бы слабоумно. Но такая возможность не всегда доступна.
А этот смысл — он чем-то другой, чем "все тьюринг-полные языки функционально эквивалентны"? Ну, т.е. следует ли из того, что всё, описанное на F#, можно сделать и на IL, оправданность применения IL для описания вот этих вот систем зависимых типов?
PJ>Ну вот мы, например, заморочились и используем IO на f#. Разумеется чистоту функций компилятор не проверяет, приходится делать через код-ревью. Тайп-классов тоже нет.
PJ>Больше писать? Да, больше. Меньше компилятор помогает? Да, меньше.
PJ>Тем не менее возможность вынести сайд-эффекты на границы домена многократно повышеат читаемость, тестируемость и, в конечном итоге, надежность.
PJ>Дебажить практически не надо, а тех редких случаях когда приходится, ты точно знаешь место где это надо посмотреть.
PJ>То же самое и в C# или любом другом языке. Либо ты больше используешь компилятор, либо дебаггер. Как-то так...
Не вполне понимаю всё же как именно выносить сайд-эффекты на "границы домена".