Монада - инструмент, который позволяет создавать цепочки вызовов функций или методов без лишних проверок данных, которые возвращаются в предыдущем шаге. Монад много, но рассмотрим только популярные -
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