Today dry-monads reaches 1.0! It started as a dependency replacement for the Kleisli gem in dry-transaction and dry-types . Later, more common monads were added, as well as support for do notation, which evaporates most of the boilerplate introduced by monads. Since the dry-* gems follow semantic versioning, this means you can consider the dry-monads API to be stable, making the gem more "production-ready". Let us show how monads can be useful in day-to-day ruby code.

Result

Result is the most widely used monad from dry-monads so far. It represents a possibly unsuccessful computation. A trivial example:

require 'dry/monads/result' class Divide include Dry :: Monads :: Result :: Mixin def call ( x , y ) if ! y . zero? Success ( x / y ) else Failure ( :division_by_zero ) end end end

Result::Mixin adds two constructors named Success(...) and Failure(...) so that you can separate the happy path from errors.

Suppose we have another math operation, square root:

require 'dry/monads/result' class Sqrt include Dry :: Monads :: Result :: Mixin def call ( x ) if ! x . negative? Success ( Math . sqrt ( x )) else Failure ( :negative_number ) end end end

Now, as with other monads, we can use bind for composition:

class DivideThenRoot def divide Divide . new end def sqrt Sqrt . new end def call ( x , y ) divide . ( x , y ). bind ( sqrt ) end end

op = DivideThenRoot . new op . ( 1.0 , 2.0 ) # => Success(0.7071067811865476) op . ( 1.0 , 0.0 ) # => Failure(:division_by_zero) op . ( - 1.0 , 2.0 ) # => Failure(:negative_number)

DivideThenRoot can be composed with other objects or methods returning Result s in a similar manner. In the end, you can use dry-matcher for processing the result (or use the Result 's API for it).

Real-life code looks the same in general but usually combines more operations together. Here it can become tedious to use bind and fmap directly. This is why we added do notation in the 1.0 release.

Do notation

The name "do" comes from Haskell, where it's a reserved word for a block of code that uses monads to compose results of several operations. We don't have first-class support for it in Ruby, but it's quite possible to emulate it using blocks. Here's a typical piece of code written with do :

require 'dry/monads/do' require 'dry/monads/result' class CreateAccount include Dry :: Monads :: Result :: Mixin include Dry :: Monads :: Do def call ( params ) values = yield validate ( params ) owner = yield create_owner ( values [ :user ]) account = yield create_account ( values [ :account ]) yield create_subscription ( account , owner ) Success ( account ) end # ... end

Here it's implied that the validate , create_owner , create_account , and create_subscription methods all return Result s. yield takes a Result value and either unwraps it if it's a Success , or interrupts the execution and immediately returns the Failure from call . With do it's extremely easy to combine results of different operations no matter the order in which they're called. This is a major step forward to making monads practically useful in Ruby.

Task

Another highlight from the release is the Task monad. Backed by concurrent-ruby , a battle-tested concurrency gem, Task can be used for composing asynchronous computations. Essentially, it's a Promise with a dry-monads-compatible interface.

require 'dry/monads/task' require 'dry/monads/do' class CreateUser include Dry :: Monads :: Task :: Mixin include Dry :: Monads :: Do def call ( email , name ) # Run two concurrent requests, wait for both to finish using Do yield validate_email ( email ), validate_name ( name ) create_user ( email , name ) end def validate_email ( email ) # Ruby 2.5+ syntax Task [ :io ] { # async email check, e.g. with an http request } end def validate_name ( name ) Task [ :io ] { # async name check } end def create_user ( email , name ) Task [ :io ] { # async create } end end

Still Ruby

Despite using concepts borrowed from other languages, dry-monads stays as much idiomatic to Ruby as possible. We have no plans to turn it into Haskell. Yet.

Maturity

The gem is pretty much complete, and has been used in production for more than two years. We don't expect any major changes to the API, since the scope of monads, in general, is limited. This means any integration code will most likely be somewhere else.

Acknowledgements

dry-monads is a combined effort of more than a dozen people. Thank you all for your work and feedback, it is much appreciated!