Монада - инструмент, который позволяет создавать цепочки вызовов функций или методов без лишних проверок данных, которые возвращаются в предыдущем шаге. Монад много, но рассмотрим только популярные -
Maybe,
Result и
Try.
*
Maybe - оборачивает значение в
Some или возвращает
None объект без значения.
*
Result - оборачивает значение в
Success или
Failure.
*
Try - оборачивает вызов кода в
Result если не было эксепшенов и в
Error, если код упал с ошибкой (которая ловится)
У каждой из монад есть 3 главных функции,
fmap,
bind и способ получить данные, которые содержит в себе монада.
*
fmap - выполняет блок, если значение монады соответствует
Success варианту, а результат выполнения блока оборачивает в ту же монаду, у которой он вызвался. Например:
Some(1).fmap(&:to_s) # => Some('1')
None().fmap(&:to_s) # => Nothing
*
bind - аналогичен
fmap, только возвращается результат выполнения блока:
Some(1).bind(&:to_s) # => '1'
Some(1).bind { |value| Success(value) } # => Success('1')
None().bind(&:to_s) # => Nothing
В руби нет монад из коробки, но существуют гемы, которые реализуют монады:
*
dry-monads*
kleisli*
tomstuart/monadsСоветую dry, как единственную поддерживаемую. К тому же, при использовании dry-validation можно легко конвертировать результат валидации в монаду, воспользовавшись экстеншеном:
Dry::Validation.load_extensions(:monads)
Это минимум, который нужен, чтобы начать использовать монады в руби приложении. Для закрепления - перепишем изначальный пример с использованием монад:
http.get(url, params) # теперь клиент возвращается Result Monad
# валидация возвращает Result, который используется для следующих вызовов
.bind { |body| validator.call(body).to_result }
# сохраняем в базу, если валидация вернула Success
.bind { |payload| Maybe(user_repository.create(payload)) }
# вызываем воркер, если сохранение вернет Some
.fmap { |user| NotificationWorker.perform_async(user.id) }
Кроме использования монад в бизнес логике, попробуйте эту абстракцию для обработки результата, который возвращается из бизнес логики. Как пример - вызов operation из экшена и последующая обработка результата в этом же экшене:
cookie_box/show.rbЧто делать с результатомПри использовании
dry-monads можно:
- вызывать на прямую
success?,
failed? или
value_or;
-
использовать `dry-matcher`;
- мой любимый вариант,
использовать `case`;
Минусы1. В отличии от условий (
if,
unless, etc) нельзя просто взять и использовать монаду. Если не знать в чем смысл абстракции и что значат
bind и
fmap - будет сложно понять код, который написан;
2. Использование монад может сильно усложнить код. Спасает опыт, а опыт получается в практике;
3. Если хотите начать использовать монады в проекте, придется прорваться через ужас в глазах коллег (причина почему я написал этот текст);
Запомнить* Монады - абстракция для чейна вызовов функций и следованию railway programming;
* Для использования монад не нужно математическое образование. Главное понять, что монада оборачивает данные в объекты с единым интерфейсом;
* Советую начать с -
Maybe,
Result и
Try;
*
fmap и
bind - методы для чейна вызовов функций
* Чрезмерное использование монад усложняет код, будьте осторожны и подходите к написанию кода с умом;
Полезное*
Как рефакторить руби код с монадами*
Algebraic Data Types & Monads in Ruby*
Monads and Ruby*
Railway Oriented Programming