Зачем жить с половиной слабореализованных фич, если можно получить полный набор?
Кроме того, про DU в сишарпе вообще пока ничего не слышал. Expression-based он тоже вряд ли станет когда-нибудь, верно? Обе эти фичи сильно способствуют стабильности и самодокументируемости кода.
Разные способы есть. Если email по бизнес логике обязателен, то, например, можно вернуть ошибку с помощью DU еще до создания этого экземпляра, но мне нравится вот такой подход:
module Email =
type EmailAddress =
private
| ValidEmail of string
| InvalidEmail of string
let ofString = function
| "validEmail" -> ValidEmail "validEmail"
| invalid -> InvalidEmail invalid
let (|ValidEmail|InvalidEmail|) = function
| ValidEmail email -> ValidEmail email
| InvalidEmail email -> InvalidEmail email
open Email
let invalid = Email.ofString "invalid"
let valid = Email.ofString "validEmail"
match invalid with
| InvalidEmail invalid -> printfn "invalid was InvalidEmail %s" invalid
| ValidEmail valid -> printfn "invalid was ValidEmail %s" valid
match valid with
| InvalidEmail invalid -> printfn "valid was InvalidEmail %s" invalid
| ValidEmail valid -> printfn "valid was ValidEmail %s" valid
Тут мы убрали возможность создавать мыльник напрямую, но оставили возможность матчить валиден ли он или нет.
Видимо, я неточно выразился.
Во-первых, F# точно не является серебрянной пулей: система типов в хаскеле, например, мощнее, а в C# есть nameof, partial classes, которые делают его более удобным для генерации code behind. Я, например, не знаю как F# работает с WPF — не пробовал.
Во-вторых, я использовал упрощенные примеры, просто потому что так проще писать статью.
Да, в F# можно объявить обыкновенный мутабельный класс, и тогда компилятор не защитит это поле в рекорде или в DU. Мой поинт не в том, что F# покроет все магической защитой, а в том, что F# позволяет легко создавать и работать с неизменяемыми структурами, в отличие от C#. Да, массив все еще изменяемый, но из коробки в F# есть неизменяемые List, Set & Map.
По поводу случайного инта — я, конечно, ни в коем случае не ожидаю, что кто-то будет случайным образом пихать аргументы в индексатор, в конце концов, проверка длины массива — одно из первых правил, которое выучивает юный программист. Я лишь добавил это как демонстрацию того, насколько функция далека от тотальности. Тем не менее, несмотря на то, что мы научнены жизнью и делать так не будем, факт остается фактом: функция написана так, что бОльшая часть диапазона входных параметров вызовет исключение.
Что касается примера с if/else, я знаю про тернарный оператор, да. И про var, я без всякой задней мысли поставил там int, ошибся, сорян. Опять-таки, пример упрощен, и я там говорю про добавление веток в будущем. Можно сделать вложенные тернарные операторы, но я так делать не люблю из-за плохой читаемости.
Давайте сделаем пример более боевым:
let myResult =
if condition then
let a = myFunc arg1 arg2
let b = myFunc2 arg3
a + b
elif condition2 then
myFunc arg4 arg5
else
myFunc2 arg3
При большом желании можно это запихать в тернарные операторы, но обычно так не делают и теряют эту проверку ветвей.
Во-первых, есть elmish xamarin. Там FP-UI. Я не говорю, что вам нужно его выбрать или что это единственный вариант, но примеры посмотреть уже можно.
Во-вторых, независимо от того, веб у вас или десктоп, в приложении есть бизнес-логика. Ее удобней делать на чистых функциях, как я и написал в статье. Потому что это стабильней и легче тестировать. Сам слой презентации вы можете описать на C#, если вам так больше нравится. Они с F# нормально уживаются даже в рамках одного солюшена.
Уже почти не важно на чем вы пишете: Python, Java, Scala. Код на любом из этих языков можно обернуть в образ и поставлять контейнером.
С одной стороны вы говорите, что не важно на чем писать, с другой стороны говорите, что ФП может быть помехой. Если вы уже используете JVM или .net, вся экосистема вам уже доступна, все фреймворки и библиотеки к вашим услугам. Разница только в сложности поддержки кода, и для многих задач ФП обеспечит более легкий в поддержке код, особенно если речь идет про потоковую обработку данных и/или конкарренси.
В С# — это, например, Entity Framework и .Net. В Java — это Hibernate и Spring. И почти каждый фреймворк нацелен на работу в привычном нам ООП стиле. Почти всегда эти решения имеют изменяемые состояния и совершенно не пригодны для работы с чистым ФП.
То, что, например, EF работает с мутацией наших моделей, все еще не означает, что мы не можем его использовать из F#. Популярное решение — impure/pure/impure sandwich. Сделайте модели, которые будет мутировать EF, они все равно даже в ООП обычно сразу мапятся в другие модели, и наш собственный код их никак не мутирует. Собственно, конкретно в F# вы можете использовать рекорды с аттрибутом [<CLIMutable>], который позволит десериализацию, но ваш код мутировать эти объекты не сможет.
вы действительно уверены, что ваше время стоит вкладывать в изучение ФП с его теорией категорий? Возможно стоит вложиться в развитие общей архитектуры системы.
Представьте себе, теория категорий в ФП тесно связана с построением архитектуры.
Остается всего одна проблема. Что с ним делать дальше? Как настроить процесс автоматизации? Как настроить “умную” балансировку? Как другим сервисом найти ваш? На эти вопросы ответа у вас может просто не быть!
Я не понял, как это связано с тем, на каком языке написан сервис. С момента, как вы его захостили и выставили публичный апи, всему остальному миру плевать, на чем внутри написан сервис, он использует его как черный ящик со стандартным протоколом коммуникации. Проблемы балансировки и service discovery тоже непонятно какое отношение имеют к парадигме программирования. Если уж на то пошло, есть Azure, который предоставляет автоскейлинг, например, для Azure Functions. И можно писать AF хоть на С#, хоть на F#, хоть на JS, не побоюсь этого слова.
Что касается вывода — да, любой инструмент хорош для подходящей задачи, спасибо. Но ничего нового вы нам тут не сказали.
Кейс редкий, но допустим.
Во-первых, коль скоро мы говорим про F#, тут можно без каких либо проблем применять ООП подход: вам доступны и традиционные мутабельные ссылочные типы, и Nullable, и куда более удобный Option<T> (который, кстати, тоже тип-сумма). И вы можете задизайнить в привычной манере.
Во-вторых, если концепция плохо работает на каком-то радикальном случае, это не значит, что концепция плоха в принципе. Любой инструмент имеет смысл в рамках решения задачи, и если конкретная задача решается с его помощью плохо — всегда можно выбрать другой.
В-третьих, кейс с большим количеством полей можно вообще спроектировать принципиально по-другому, например, как массив объектов нашего типа-суммы (в котором 10 кейсов), и валидировать, что длина больше 0. А лучше вообще завернуть это в еще один тип-сумму type MyInfo = | Invalid | Info of MyDU list
Короче говоря, если у вас нет миллионов опциональных полей, из которых только сотня обязательна, то такой дизайн поможет перетащить некоторые проблемы из рантайма в компайл тайм, а это огромный бонус.
Можно, только зачем? Один из весомых бонусов подхода из статьи — код строго соответствует бизнес логике, вы читаете код и понимаете правило. Когда же вы видите класс, у которого есть оба поля, и они оба необязательные, а консистентность обеспечивается наличием двух конструкторов — новый разработчик добавит конструктор по умолчанию, и ваша защита на этом кончилась.
Причем это не будет выглядеть ошибкой или ломающим изменением — структура объекта предполагает одновременное отсутствие обоих полей, конструктор по умолчанию лишь позволяет это состояние создать.
И напротив, когда дизайн как в статье, на Discriminated Unions (они же типы-суммы и размеченные объединения), он говорит разработчику, не знакомому с кодом, что одно из полей обязательное. И добавление нового кейса без обязательных полей уже в явном виде говорит, что мы меняем бизнес логику
Все просто: если разработчик позиционирует себя в профессиональном смысле как C# developer, то независимо от того, знает ли он SQL/NoSQL/JS/Azure и тд, он все равно большую часть рабочего дня пишет код на C#, и подавляющее большинство его задач тесно связаны с C#. И если все то, что у него отнимает больше всего рабочего времени, можно решить проще, быстрее и надежнее, то ухватиться за эту возможность в долгосрочной перспективе будет выгодно и ему, и бизнесу.
Тем более, что это даже не будет радикальным изменением, как например, переход на хаскель или го. Это та же платформа с той же инфраструктурой. C# и F# можно даже в одном солюшне сочетать, так что плавный переход возможен даже на монолитных приложениях.
И освободившееся от багфикса и рефакторинга время можно будет потратить в том числе на изучение других технологий.
Я не делаю вид, что этого не надо и не говорю, что F# идеален. Да, было бы круто иметь инструменты вроде решарпера (я, кстати, им и в C# не пользуюсь, но все равно), было бы здорово, если бы debug tools были столь же хороши или даже лучше, чем в C#. На данный момент в F# нет оператора `nameof`, к сожалению.
Я говорю, что несмотря на отсутствие/отставание этих инструментов эффективность решения бизнес задач на F# все еще выше, чем на C#.
Фейсбук активно использует хаскель для борьбы со спамом и очень этим доволен. Тут уже несколько раз вспоминали про Вагифа, а ведь у них уже было все написано на C# и они все равно пошли на такой риск для бизнеса, как не просто все переписать, а еще и переписать на другом языке, к тому же, менее популярном. И остались довольны. Насколько я знаю, шведская компания Record Union тоже собиралась переписывать то, что у них есть, с C# на F#. Rust на stackoverflow является самым любимым программистами языком.
Если захотите найти аргументы, чтобы не учить F# или ФП в целом, вы их найдете. Только зачем?
То есть, возможность написать запрос к google API в 7 строк — это хорошо, но наличие библиотеки googleAPI — лучше.
Спорное утверждение. Использование библиотек помимо очевидных плюсов имеет и ряд минусов. Во-первых, в большинстве случаев библиотеки содержат гораздо больше функционала, чем требуется в конкретном проекте, а это значит, что размер бинарей увеличивается, и кому-то это важно. Во-вторых, изучение библиотеки тоже требует времени, в редких случаях в совокупности больше, чем написание нужного кода своими силами.
В-третьих, библиотека контролируется другой стороной, так что если там есть какой-то баг, который сильно мешает жить — придется подождать.
Если вместо всего этого можно обойтись 7 строчками своего кода — я бы выбрал этот вариант.
У F# есть какие-то «инфраструктурные» преимущества?
О каких инфраструктурных преимуществах идет речь? Набор библиотек такой же, как для C#. Среда разработки та же.
У F# нет решарпера, но так он и не так нужен. У F# ниже дебагабилити, но и дебажить приходится гораздо реже.
Для тех задач, которые может закрыть F# — у разработчика уже есть C#
Верно. Но на F# эти задачи можно решить проще, быстрее и надежнее. Если это для вас не аргумент, тогда мне больше нечего предложить.
Главным образом это применимо к Discriminated Unions. Вот тут можно глянуть пример goo.gl/V8pozz
Даже после чистки всех автосгенеренных аттрибутов и форматирования все равно счет строк идет на сотни
Зачем жить с половиной слабореализованных фич, если можно получить полный набор?
Кроме того, про DU в сишарпе вообще пока ничего не слышал. Expression-based он тоже вряд ли станет когда-нибудь, верно? Обе эти фичи сильно способствуют стабильности и самодокументируемости кода.
Разные способы есть. Если email по бизнес логике обязателен, то, например, можно вернуть ошибку с помощью DU еще до создания этого экземпляра, но мне нравится вот такой подход:
Тут мы убрали возможность создавать мыльник напрямую, но оставили возможность матчить валиден ли он или нет.
Видимо, я неточно выразился.
Во-первых, F# точно не является серебрянной пулей: система типов в хаскеле, например, мощнее, а в C# есть
nameof, partial classes, которые делают его более удобным для генерации code behind. Я, например, не знаю как F# работает с WPF — не пробовал.Во-вторых, я использовал упрощенные примеры, просто потому что так проще писать статью.
Да, в F# можно объявить обыкновенный мутабельный класс, и тогда компилятор не защитит это поле в рекорде или в DU. Мой поинт не в том, что F# покроет все магической защитой, а в том, что F# позволяет легко создавать и работать с неизменяемыми структурами, в отличие от C#. Да, массив все еще изменяемый, но из коробки в F# есть неизменяемые
List,Set&Map.По поводу случайного инта — я, конечно, ни в коем случае не ожидаю, что кто-то будет случайным образом пихать аргументы в индексатор, в конце концов, проверка длины массива — одно из первых правил, которое выучивает юный программист. Я лишь добавил это как демонстрацию того, насколько функция далека от тотальности. Тем не менее, несмотря на то, что мы научнены жизнью и делать так не будем, факт остается фактом: функция написана так, что бОльшая часть диапазона входных параметров вызовет исключение.
Что касается примера с
if/else, я знаю про тернарный оператор, да. И проvar, я без всякой задней мысли поставил тамint, ошибся, сорян. Опять-таки, пример упрощен, и я там говорю про добавление веток в будущем. Можно сделать вложенные тернарные операторы, но я так делать не люблю из-за плохой читаемости.Давайте сделаем пример более боевым:
При большом желании можно это запихать в тернарные операторы, но обычно так не делают и теряют эту проверку ветвей.
Во-вторых, независимо от того, веб у вас или десктоп, в приложении есть бизнес-логика. Ее удобней делать на чистых функциях, как я и написал в статье. Потому что это стабильней и легче тестировать. Сам слой презентации вы можете описать на C#, если вам так больше нравится. Они с F# нормально уживаются даже в рамках одного солюшена.
Сумбур какой-то.
С одной стороны вы говорите, что не важно на чем писать, с другой стороны говорите, что ФП может быть помехой. Если вы уже используете JVM или .net, вся экосистема вам уже доступна, все фреймворки и библиотеки к вашим услугам. Разница только в сложности поддержки кода, и для многих задач ФП обеспечит более легкий в поддержке код, особенно если речь идет про потоковую обработку данных и/или конкарренси.
То, что, например, EF работает с мутацией наших моделей, все еще не означает, что мы не можем его использовать из F#. Популярное решение — impure/pure/impure sandwich. Сделайте модели, которые будет мутировать EF, они все равно даже в ООП обычно сразу мапятся в другие модели, и наш собственный код их никак не мутирует. Собственно, конкретно в F# вы можете использовать рекорды с аттрибутом
[<CLIMutable>], который позволит десериализацию, но ваш код мутировать эти объекты не сможет.Представьте себе, теория категорий в ФП тесно связана с построением архитектуры.
Я не понял, как это связано с тем, на каком языке написан сервис. С момента, как вы его захостили и выставили публичный апи, всему остальному миру плевать, на чем внутри написан сервис, он использует его как черный ящик со стандартным протоколом коммуникации. Проблемы балансировки и service discovery тоже непонятно какое отношение имеют к парадигме программирования. Если уж на то пошло, есть Azure, который предоставляет автоскейлинг, например, для Azure Functions. И можно писать AF хоть на С#, хоть на F#, хоть на JS, не побоюсь этого слова.
Что касается вывода — да, любой инструмент хорош для подходящей задачи, спасибо. Но ничего нового вы нам тут не сказали.
Кейс редкий, но допустим.
Во-первых, коль скоро мы говорим про F#, тут можно без каких либо проблем применять ООП подход: вам доступны и традиционные мутабельные ссылочные типы, и
Nullable, и куда более удобныйOption<T>(который, кстати, тоже тип-сумма). И вы можете задизайнить в привычной манере.Во-вторых, если концепция плохо работает на каком-то радикальном случае, это не значит, что концепция плоха в принципе. Любой инструмент имеет смысл в рамках решения задачи, и если конкретная задача решается с его помощью плохо — всегда можно выбрать другой.
В-третьих, кейс с большим количеством полей можно вообще спроектировать принципиально по-другому, например, как массив объектов нашего типа-суммы (в котором 10 кейсов), и валидировать, что длина больше 0. А лучше вообще завернуть это в еще один тип-сумму
type MyInfo = | Invalid | Info of MyDU listКороче говоря, если у вас нет миллионов опциональных полей, из которых только сотня обязательна, то такой дизайн поможет перетащить некоторые проблемы из рантайма в компайл тайм, а это огромный бонус.
Можно, только зачем? Один из весомых бонусов подхода из статьи — код строго соответствует бизнес логике, вы читаете код и понимаете правило. Когда же вы видите класс, у которого есть оба поля, и они оба необязательные, а консистентность обеспечивается наличием двух конструкторов — новый разработчик добавит конструктор по умолчанию, и ваша защита на этом кончилась.
Причем это не будет выглядеть ошибкой или ломающим изменением — структура объекта предполагает одновременное отсутствие обоих полей, конструктор по умолчанию лишь позволяет это состояние создать.
И напротив, когда дизайн как в статье, на
Discriminated Unions(они же типы-суммы и размеченные объединения), он говорит разработчику, не знакомому с кодом, что одно из полей обязательное. И добавление нового кейса без обязательных полей уже в явном виде говорит, что мы меняем бизнес логикуЕсли убрать дилей вообще, выглядит это так:
Если поставить 100 мс, будет так:
Тем более, что это даже не будет радикальным изменением, как например, переход на хаскель или го. Это та же платформа с той же инфраструктурой. C# и F# можно даже в одном солюшне сочетать, так что плавный переход возможен даже на монолитных приложениях.
И освободившееся от багфикса и рефакторинга время можно будет потратить в том числе на изучение других технологий.
Я говорю, что несмотря на отсутствие/отставание этих инструментов эффективность решения бизнес задач на F# все еще выше, чем на C#.
Фейсбук активно использует хаскель для борьбы со спамом и очень этим доволен. Тут уже несколько раз вспоминали про Вагифа, а ведь у них уже было все написано на C# и они все равно пошли на такой риск для бизнеса, как не просто все переписать, а еще и переписать на другом языке, к тому же, менее популярном. И остались довольны. Насколько я знаю, шведская компания Record Union тоже собиралась переписывать то, что у них есть, с C# на F#. Rust на stackoverflow является самым любимым программистами языком.
Если захотите найти аргументы, чтобы не учить F# или ФП в целом, вы их найдете. Только зачем?
Спорное утверждение. Использование библиотек помимо очевидных плюсов имеет и ряд минусов. Во-первых, в большинстве случаев библиотеки содержат гораздо больше функционала, чем требуется в конкретном проекте, а это значит, что размер бинарей увеличивается, и кому-то это важно. Во-вторых, изучение библиотеки тоже требует времени, в редких случаях в совокупности больше, чем написание нужного кода своими силами.
В-третьих, библиотека контролируется другой стороной, так что если там есть какой-то баг, который сильно мешает жить — придется подождать.
Если вместо всего этого можно обойтись 7 строчками своего кода — я бы выбрал этот вариант.
О каких инфраструктурных преимуществах идет речь? Набор библиотек такой же, как для C#. Среда разработки та же.
У F# нет решарпера, но так он и не так нужен. У F# ниже дебагабилити, но и дебажить приходится гораздо реже.
Верно. Но на F# эти задачи можно решить проще, быстрее и надежнее. Если это для вас не аргумент, тогда мне больше нечего предложить.
Даже после чистки всех автосгенеренных аттрибутов и форматирования все равно счет строк идет на сотни