Maybe

The Maybe monad is used when a series of computations could return nil at any point.

bind

Applies a block to a monadic value. If the value is Some then calls the block passing the unwrapped value as an argument. Returns itself if the value is None.

extend Dry::Monads[:maybe]

maybe_user = Maybe(user).bind do |u|
  Maybe(u.address).bind do |a|
    Maybe(a.street)
  end
end

# If user with address exists
# => Some("Street Address")
# If user or address is nil
# => None()

# You also can pass a proc to #bind

add_two = -> (x) { Maybe(x + 2) }

Maybe(5).bind(add_two).bind(add_two) # => Some(9)
Maybe(nil).bind(add_two).bind(add_two) # => None()

fmap

Similar to bind but works with blocks/methods that returns unwrapped values (i.e. not Maybe instances).

extend Dry::Monads[:maybe]

Maybe(10).fmap { |x| x + 5 }.fmap { |y| y * 2 }
# => Some(30)

In 1.x Maybe#fmap coerces nil values returned from blocks to None. This behavior will be changed in 2.0. This will be done because implicit coercion violates the functor laws which in order can lead to a surpising (not in a good sense) behavior. If you expect a block to return nil, use Maybe#maybe added in 1.3.

maybe

Almost identical to Maybe#fmap but maps nil to None. This is similar to how the &. operator works in Ruby but does wrapping:

extend Dry::Monads[:maybe]

Maybe(user).maybe(&:address).maybe(&:street)

# If user with address exists
# => Some("Street Address")
# If user or address is nil
# => None()

value!

You always can extract the result by calling value!. It will raise an error if you call it on None. You can use value_or for safe unwrapping.

extend Dry::Monads[:maybe]

Some(5).fmap(&:succ).value! # => 6

None().fmap(&:succ).value!
# => Dry::Monads::UnwrapError: value! was called on None

value_or

Has one argument, unwraps the value in case of Some or returns the argument value back in case of None. It’s a safe and recommended way of extracting values.

extend Dry::Monads[:maybe]

add_two = -> (x) { Maybe(x + 2) }

Maybe(5).bind(add_two).value_or(0) # => 7
Maybe(nil).bind(add_two).value_or(0) # => 0

Maybe(nil).bind(add_two).value_or { 0 } # => 0

or

The opposite of bind.

extend Dry::Monads[:maybe]

add_two = -> (x) { Maybe(x + 2) }

Maybe(5).bind(add_two).or(Some(0)) # => Some(7)
Maybe(nil).bind(add_two).or(Some(0)) # => Some(0)

Maybe(nil).bind(add_two).or { Some(0) } # => Some(0)

and

Two values can be chained using .and:

extend Dry::Monads[:maybe]

Some(5).and(Some(10)) { |x, y| x + y } # => Some(15)
Some(5).and(None) { |x, y| x + y }     # => None()
None().and(Some(10)) { |x, y| x + y }  # => None()

Some(5).and(Some(10)) # => Some([5, 10])
Some(5).and(None())   # => None()

flatten

To remove one level of nesting:

extend Dry::Monads[:maybe]

Some(Some(10)).flatten # => Some(10)
Some(None()).flatten   # => None()
None().flatten         # => None()

to_result

Maybe values can be converted to Result objects:

extend Dry::Monads[:maybe, :result]

Some(10).to_result # => Success(10)
None().to_result # => Failure()
None().to_result(:error) # => Failure(:error)
None().to_result { :block_value } # => Failure(:block_value)