Either
The Either monad helps you manage errors at the end of a chain of instructions. An Either(E, T) is one of two variants: Right (a success of type T) or Left (an error of type E).
Both variants are parameterized with both the error type E and the success type T. This lets a method return either variant while the types still unify:
def divide(a : Int32, b : Int32) : Monads::Either(String, Int32)
if b == 0
Monads::Left(String, Int32).new("Division by zero")
else
Monads::Right(String, Int32).new(a // b)
end
endRight
A success value:
Monads::Right(String, Int32).new(42)Left
An error value:
Monads::Left(String, Int32).new("User password is incorrect")LeftException
A specialized Left whose error type is always Exception. It is what Try and Task produce when code raises:
Monads::LeftException(Int32).new(DivisionByZeroError.new)Transforming values
fmap (aliased as map) and bind (aliased as flat_map) operate on Right and pass Left/LeftException through untouched:
Monads::Right(String, Int32).new(21).fmap(->(x : Int32) { x * 2 }) # => Right(42)
Monads::Left(String, Int32).new("nope").fmap(->(x : Int32) { x * 2 }) # => Left("nope")
# map / flat_map are aliases
Monads::Right(String, Int32).new(21).map(->(x : Int32) { x * 2 }) # => Right(42)Putting it together
divide(10, 2)
.fmap(->(x : Int32) { x * 2 })
.value_or(->(err : String) { 0 }) # => 10
divide(10, 0)
.fmap(->(x : Int32) { x * 2 })
.value_or(->(err : String) { 0 }) # => 0Pattern matching with fold
divide(10, 2).fold(
->(value : Int32) { "Success: #{value}" },
->(error : String) { "Error: #{error}" }
) # => "Success: 5"Checking the variant
Monads::Right(String, Int32).new(1).right? # => true
Monads::Right(String, Int32).new(1).left? # => falseRecovering with or
or returns the receiver if it is Right, otherwise the alternative:
Monads::Left(String, Int32).new("e").or(Monads::Right(String, Int32).new(0)) # => Right(0)See the Either API reference for every method, and the Error handling example for a fuller walkthrough.