shterrett / failure

Maybe and Either for Ruby

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.message }
)

#=> 100

Either.match(left,
  right: ->(i) { i * 100 },
  left: ->(e) { e.message }
)

#=> "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.

About

Maybe and Either for Ruby


Languages

Language:Ruby 100.0%