class Functional::Either

The ‘Either` type represents a value of one of two possible types (a disjoint union). It is an immutable structure that contains one and only one value. That value can be stored in one of two virtual position, `left` or `right`. The position provides context for the encapsulated data.

One of the main uses of ‘Either` is as a return value that can indicate either success or failure. Object oriented programs generally report errors through either state or exception handling, neither of which work well in functional programming. In the former case, a method is called on an object and when an error occurs the state of the object is updated to reflect the error. This does not translate well to functional programming because they eschew state and mutable objects. In the latter, an exception handling block provides branching logic when an exception is thrown. This does not translate well to functional programming because it eschews side effects like structured exception handling (and structured exception handling tends to be very expensive). `Either` provides a powerful and easy-to-use alternative.

A function that may generate an error can choose to return an immutable ‘Either` object in which the position of the value (left or right) indicates the nature of the data. By convention, a `left` value indicates an error and a `right` value indicates success. This leaves the caller with no ambiguity regarding success or failure, requires no persistent state, and does not require expensive exception handling facilities.

‘Either` provides several aliases and convenience functions to facilitate these failure/success conventions. The `left` and `right` functions, including their derivatives, are mirrored by `reason` and `value`. Failure is indicated by the presence of a `reason` and success is indicated by the presence of a `value`. When an operation has failed the either is in a `rejected` state, and when an operation has successed the either is in a `fulfilled` state. A common convention is to use a Ruby `Exception` as the `reason`. The factory method `error` facilitates this. The semantics and conventions of `reason`, `value`, and their derivatives follow the conventions of the Concurrent Ruby gem.

The ‘left`/`right` and `reason`/`value` methods are not mutually exclusive. They can be commingled and still result in functionally correct code. This practice should be avoided, however. Consistent use of either `left`/`right` or `reason`/`value` against each `Either` instance will result in more expressive, intent-revealing code.

@example

require 'uri'

def web_host(url)
  uri = URI(url)
  if uri.scheme != 'http'
    Functional::Either.left('Invalid HTTP URL')
  else
    Functional::Either.right(uri.host)
  end
end

good = web_host('http://www.concurrent-ruby.com')
good.right? #=> true
good.right  #=> "www.concurrent-ruby"
good.left #=> nil

good = web_host('bogus')
good.right? #=> false
good.right  #=> nil
good.left #=> "Invalid HTTP URL"

@see functionaljava.googlecode.com/svn/artifacts/3.0/javadoc/fj/data/Either.html Functional Java @see hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html Haskell Data.Either @see ruby-concurrency.github.io/concurrent-ruby/Concurrent/Obligation.html Concurrent Ruby

@!macro thread_safe_immutable_object

Constants

NO_VALUE

@!visibility private

Public Class Methods

error(message = nil, clazz = StandardError) click to toggle source

Create an ‘Either` with the left value set to an `Exception` object complete with message and backtrace. This is a convenience method for supporting the reason/value convention with the reason always being an `Exception` object. When no exception class is given `StandardError` will be used. When no message is given the default message for the given error class will be used.

@example

either = Functional::Either.error("You're a bad monkey, Mojo Jojo")
either.fulfilled? #=> false
either.rejected?  #=> true
either.value      #=> nil
either.reason     #=> #<StandardError: You're a bad monkey, Mojo Jojo>

@param [String] message The message for the new error object. @param [Exception] clazz The class for the new error object. @return [Either] A new either with an error object as the left value.

# File lib/functional/either.rb, line 133
def error(message = nil, clazz = StandardError)
  ex = clazz.new(message)
  ex.set_backtrace(caller)
  left(ex)
end
iff(lvalue, rvalue, condition = NO_VALUE) { |: !! condition| ... } click to toggle source

If the condition satisfies, return the given A in left, otherwise, return the given B in right.

@param [Object] lvalue The left value to use if the condition satisfies. @param [Object] rvalue The right value to use if the condition does not satisfy. @param [Boolean] condition The condition to test (when no block given). @yield The condition to test (when no condition given).

@return [Either] A constructed either based on the given condition.

@raise [ArgumentError] When both a condition and a block are given.

# File lib/functional/either.rb, line 204
def self.iff(lvalue, rvalue, condition = NO_VALUE)
  raise ArgumentError.new('requires either a condition or a block, not both') if condition != NO_VALUE && block_given?
  condition = block_given? ? yield : !! condition
  condition ? left(lvalue) : right(rvalue)
end
left(value) click to toggle source

Construct a left value of either.

@param [Object] value The value underlying the either. @return [Either] A new either with the given left value.

# File lib/functional/either.rb, line 101
def left(value)
  new(value, true).freeze
end
Also aliased as: reason
new(value, is_left) click to toggle source

Create a new Either wil the given value and disposition.

@param [Object] value the value of this either @param [Boolean] is_left is this a left either or right?

@!visibility private

Calls superclass method
# File lib/functional/either.rb, line 218
def initialize(value, is_left)
  super
  @is_left = is_left
  hsh = is_left ? {left: value, right: nil} : {left: nil, right: value}
  set_data_hash(hsh)
  set_values_array(hsh.values)
  ensure_ivar_visibility!
end
reason(value)
Alias for: left
right(value) click to toggle source

Construct a right value of either.

@param [Object] value The value underlying the either. @return [Either] A new either with the given right value.

# File lib/functional/either.rb, line 110
def right(value)
  new(value, false).freeze
end
Also aliased as: value
value(value)
Alias for: right

Public Instance Methods

either(lproc, rproc) click to toggle source

The catamorphism for either. Folds over this either breaking into left or right.

@param [Proc] lproc The function to call if this is left. @param [Proc] rproc The function to call if this is right. @return [Object] The reduced value.

# File lib/functional/either.rb, line 190
def either(lproc, rproc)
  left? ? lproc.call(left) : rproc.call(right)
end
fulfilled?()
Alias for: right?
left() click to toggle source

Projects this either as a left.

@return [Object] The left value or ‘nil` when `right`.

# File lib/functional/either.rb, line 143
def left
  left? ? to_h[:left] : nil
end
Also aliased as: reason
left?() click to toggle source

Returns true if this either is a left, false otherwise.

@return [Boolean] ‘true` if this either is a left, `false` otherwise.

# File lib/functional/either.rb, line 159
def left?
  @is_left
end
Also aliased as: reason?, rejected?
reason()
Alias for: left
reason?()
Alias for: left?
rejected?()
Alias for: left?
right() click to toggle source

Projects this either as a right.

@return [Object] The right value or ‘nil` when `left`.

# File lib/functional/either.rb, line 151
def right
  right? ? to_h[:right] : nil
end
Also aliased as: value
right?() click to toggle source

Returns true if this either is a right, false otherwise.

@return [Boolean] ‘true` if this either is a right, `false` otherwise.

# File lib/functional/either.rb, line 168
def right?
  ! left?
end
Also aliased as: value?, fulfilled?
swap() click to toggle source

If this is a left, then return the left value in right, or vice versa.

@return [Either] The value of this either swapped to the opposing side.

# File lib/functional/either.rb, line 177
def swap
  if left?
    self.class.send(:new, left, false)
  else
    self.class.send(:new, right, true)
  end
end
value()
Alias for: right
value?()
Alias for: right?