Pull to refresh

Comments 22

Зато в награду за наши страдания мы получаем гарантированное отсутствие
race condition (состояние гонки) при параллельной обработке (безо всяких мутексов и borrow checker'ов)


Ну хорошо. А как на имутабельных объектах сделать такую элементарщину, как общий счетчик?
Общий счетчик чего? И как он будет использоваться?
От ответа на эти вопросы зависит способ (в крайнем случае — через эффекты).
Общий счётчик чего угодно. Например, веб-сервер принимает запрос типа GET /random, запрос дергается с нагрузкой 10 запросов в секунду, на каждый полученный запрос создается новый поток (или берется из пула — не суть важно), поток по какому-нибудь алгоритму вычисляет random-число и возвращает его в респонзе, но при завершении расчёта поток должен инкрементировать общий счётчик «кол-ва выполненных расчётов». Инкремент — это неатомарная операция, т.к. здесь две операции: надо сначала получить значение счётчика, а затем увеличить его на 1. Скажем, в Джаве классическим способом это делается через превращение операции в атомарную: например инкапсуляцией в синхронизированный метод или синхронизацией этих двух операций на замке (lock), либо с помощью atomic-примитивов, имеющих атомарные методы типа incrementAndGet (реализованными на более низком уровне чем Джава — как он реализован я не знаю, в JDK его не видно, т.к. он «native», возможно кстати тоже иммутабельно), при этом счётчик — это мутабельная переменная. А как эту задачу можно решить иммутабельным способом?
В такой постановке — это классический эффект — вы меняете внешнюю среду. Это ничем не отличается от работы с БД в ФЯП. Работу с эффектами вы оборачиваете в какую-нибудь монаду а-ля IO и дальше работаете, как обычно.
Вот если бы этот счетчик как-то хотели использовать в работе самого приложения — тогда ситуация чуть сложнее, но тоже разрешима.
Ну да, хотелось бы этот счётчик использовать где-то помимо операции инкремента. Например, отдельный поток считывает его и логирует в файл или на график мониторинга каждые 10 минут. В Джаве для атомарного считывания можно просто пометить мутабельное поле ключевым словом volatile, или тоже синхронизировать доступ средствами языка (если этого не сделать, то можно прочитать нецелостную информацию, например если это 64 битное значение, может быть разрыв по 32-битным словам). А здесь как это сделать?
Используйте ради бога в отдельном потоке, как угодно! Этот поток будет читать вашу монаду IO и получать счетчик.
Суть в том, что низкоуровневая работа с эффектами строго ограничена библиотечной функцией. Остальной код не видит этого.
Тут важно только заметить, что речь не о потоках исполнения, а потоках данных. Сколько реально потоков исполнения обрабатывают этот поток данных — это вопрос отдельный. Может по одному на data-flow, а может по 20… Может вообще один на всех.

При большом желании в хасткеле вы можете использовать MVar (это аналог volatile). Но это верный способ рано или поздно выстрелить себе в ногу.
Предположим, у вас есть клевая библиотека по сериализации и есть другая не очень клевая легаси библиотека ORM, которая фигачит вам кучу POJO объектов. И вам стало нужно эти объекты сериализовать первой библиотекой. Но вот беда: ваши POJO объекты не реализуют нужный интерфейс MyCoolSerializable.

Если я вас понял, то у в этом примере у Вас есть библиотека с тайплкассом, библиотека со структурой и вы реализуете тайпкласс из одной библиотеки для структур из другой. В Rust это называют Orphan Rules, которые заключаются в том, что либо нельзя реализовать "чужой" трейт (==тайпкласс) для "чужой" структуры. Что кажется печальным и, на мой взгляд, снижает гибкость.


Вопрос: а как с этим обстоят дела в более труЪ ФП языках, таких как Haskell, Scala итп? Насколько это реализуемо и насколько это распространенная практика?

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

В хаскеле — язык позволяет так делать, но это не рекомендуется. В скала все проще, насколько я понимаю. Там тайпклассы эмулируются через implicit. И уже правила разруливания implicit помогают выбрать конкретный инстанс тайпкласса.

И ещё, а расскажите, можно ли бизнесовые приложения полностью писать в функциональном стиле, без ООП и всех его SOLID/GRASP/GoF-практик?
Можно, но приходится немного изворачиваться. ООП очень хорошо ложится на UML/BPMN. А вот при натягивании UML на ФЯП, приходится сильно включать голову, что не всем под силу. Поэтому и рождаются языки типа OCaml и Scala, которые дают пытаются дать вам сразу ФП+ООП. Но в любом случае скрещивание ежа с ужом требует отдельного внимания. Вот тут-то как раз очень важно понимать преимущества и недостатки каждого подхода и уместно их использовать по очереди, не допуская смешивания. При этом стремиться все же стоит к ФП, имхо.
А зачем вам UML и BPMN? Ну в смысле, я согласен, что бывает удобно, но разве это непременный атрибут бизнес приложений? Кроме всего прочего, практически со всеми языками, в основе которых диаграммы, бывают проблемы в разработке, пототому что очень плохо с их версионированием (потому что не текст).

Что касается GoF, и частично SOLID, то если у вас скажем функции поддерживаются как first class citizen в языке, то вам многие паттерны GoF просто тривиально не нужны. С SOLID там чуть иначе, они (некоторые принципы) просто более общие, чем ООП, и применяются к ФП точно так же. А некоторые нет, или применимы — но результат выглядит иначе. Но опять же, тут нужно понимать, что SOLID это не более чем общие принципы, в явном виде в коде они практически не видны.
можно конечно без UML. Но бизнес задача чаще всего описывается именно в виде объектов и отношений между ними. Поэтому переносится на ООП проще, чем на ФП. Только в этом отличие.
>Но бизнес задача чаще всего описывается именно в виде объектов и отношений между ними.
Ну если такие выводы делать, получается что я за последние 15 лет только один раз решал бизнес-задачи — потому что UML применялся ровно один раз (и тот раз я бы не назвал полностью успешным). При этом реально вся работа была на бизнес, причем достаточно разный.

Ну то есть, что это удобнее (местами) — я согласен. Те же ER диаграммы — частный случай UML, и наверное самые распространенные.

Но вопрос же был — можно ли писать без UML? На мой взгляд ответ очевиден. Есть куча других способов представления моделей данных, и в виде кода, и без диаграмм, и без UML.

Ну и наконец, а почему в ФП парадигме нужно писать без UML? Ведь очень часто диаграммы используются как средство для генерации кода. А генерировать код можно и в ФП парадигме, уж этому-то точно ничто не мешает. И взять тот же BPMN, если уж хочется, и встроить его в ФП снова — тоже почему нет? То что большинство реализаций на ООП — мне кажется не более чем «так сложилось».
Поэтому чаще всего в реальных применениях встречается foldLeft, который более предсказуем
Я вообще не в теме, но всё же понимаю, что тот, кто нуждается в этих версиях fold, «делает что-то неправильно» в смысле чистоты функционального подхода
У моноида есть младший брат «Полудурок» «Полугруппа». Только не спрашивайте меня, почему его так назвали
Правильно, спросите википедию
Вообще говоря, экспонента вместе с братом натуральным логарифмом не могут быть гомоморфизмами double <-> double в общем случае, поскольку с одной стороны будут все вещественные числа, а с другой — только положительные, гомоморфизм же подразумевает двустороннее отображение всего множества

P.S. дочитал до оговорок по этому поводу
UFO just landed and posted this here
Отличная статья. Правда немного не хватает примеров, как все эти монады, лифты и линзы выглядят в коде.
Не хотите сделать продолжение? Например, разобрать простенькое приложение и показать, где какой инструмент стоит применить?

Ваша серия из двух статей - лучшее, что я прочёл по ФП для чайников. Спасибо.

Sign up to leave a comment.

Articles