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_street = 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)
There's an alias operator for or
:
extend Dry::Monads[:maybe]
None() | Some(1) | Some(2) # => Some(1)
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)
filter
Maybe#filter
runs a predicate against the wrapped value. Returns None
if the result is false:
Some(3).filter(&:odd?) # => Some(3)
Some(3).filter(&:even?) # => None
# no block given
Some(3 == 5).filter # => None