Failure is always an option
This library implements ruby-ified Maybe and Either types. Both capture
nil and Exception and return an object that implements the functor and monad
interfaces.
Usage
Maybe
Maybe.new takes a block and returns Just <value> on success and Nothing if
the block either returns nil or raises an exception.
Maybe.new do
[1, 2, 3].first
end
#=> Just 1
Maybe.new do
[].first
end
#=> Nothing
#map is implemented on Just and Nothing, and is the functor fmap:
map :: Maybe a -> (a -> b) -> Maybe b
Maybe.new do
[1, 2, 3].first
end.map do |i|
i * 100
end
#=> Just 100
Maybe.new do
[].first
end.map do |i|
i * 100
end
#=> Nothing
#flat_map is implemented on Just and Nothing, and is the monad bind:
flat_map :: Maybe a -> (a -> Maybe b) -> Maybe b
Maybe.new do
[[1], [2], [3]].first
end.flat_map do |i|
Maybe.new { i.first }
end
#=> Just 1
Maybe.new do
[1, 2, 3].first
end.flat_map do |i|
Maybe.new { i.first } # raises NoMethodError
end
#=> Nothing
Maybe.match takes the maybe, a proc for the Just and a proc of no arguments
for the Nothing and returns the value of the associated variant. Because there
is no value for Nothing, a default value may be provided instead of a proc.
match :: Maybe a -> (a -> b) -> b -> b
just = Maybe.new do
[1, 2, 3].first
end
nothing = Maybe.new do
[].first
end
Maybe.match(just,
just: ->(i) { i + 1 },
nothing: 0
)
#=> 2
Maybe.match(nothing,
just: ->(i) { i + 1 },
nothing: 0
)
#=> 0
Either
Either is similar to Maybe except that it preserves an error message from a
captured exception, or (trivially) the nil returned from a failed function.
Either.new takes a block and returns Right <value> if the block "succeeds",
and Left <err | nil> if the block raises or returns nil.
Either.new do
[1, 2, 3].first
end
#=> Right 1
Either.new do
1.first
end
#=> Left NoMethodError
#map is implemented on Right and Left and is the functor fmap
map :: Either a b -> (b -> c) -> Either a c
Either.new do
[1, 2, 3].first
end.map do |i|
i * 100
end
#=> Right 100
Either.new do
1.first
end.map do |i|
i * 100
end
#=> Left NoMethodError
#flat_map is implemented on Right and Left and is the monad bind
flat_map :: Either a b -> (b -> Either a c) -> Either a c
Either.new do
[[1], [2], [3]].first
end.flat_map do |i|
Either.new { i.first }
end
#=> Right 1
Either.new do
[1, 2, 3].first
end.flat_map do |i|
Either.new { i.first }
end
#=> Left NoMethodError
Either.match takes an either, a proc for Right and a proc for Left,
evaluates the proc corresponding to the variant, and returns the result.
match :: Either a b -> (a -> c) (b -> c) -> c
right = Either.new do
[1, 2, 3].first
end
left = Either.new do
1.first
end
Either.match(right,
right: ->(i) { i * 100 },
left: ->(e) { e. }
)
#=> 100
Either.match(left,
right: ->(i) { i * 100 },
left: ->(e) { e. }
)
#=> "NoMethodError (undefined method `first' for 1:Integer)"
Type Safety
There isn't any. Users are free to reach inside the classes using
instance_variable_get or send, and users can use flat_map with a block
that doesn't return an either to "unwrap" the value. And nothing constrains the
two procs in .match to return the same type. If we wanted type safety, we'd
use Haskell or Rust. But, just like everything else in ruby, this can be used to
make programming nicer.