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