Class: Functional::Either

Inherits:
Object
  • Object
show all
Includes:
AbstractStruct
Defined in:
lib/functional/either.rb

Overview

Note:

This is a write-once, read-many, thread safe object that can be used in concurrent systems. Thread safety guarantees cannot be made about objects contained within this object, however. Ruby variables are mutable references to mutable objects. This cannot be changed. The best practice it to only encapsulate immutable, frozen, or thread safe objects. Ultimately, thread safety is the responsibility of the programmer.

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.

Examples:


require 'uri'

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

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

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

See Also:

Since:

  • 1.0.0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#valuesArray (readonly) Originally defined in module AbstractStruct

Returns the values of all record fields in order, frozen

Since:

  • 1.0.0

Class Method Details

.error(message = nil, clazz = StandardError) ⇒ Either

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.

Examples:


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>

Since:

  • 1.0.0



134
135
136
137
138
# File 'lib/functional/either.rb', line 134

def error(message = nil, clazz = StandardError)
  ex = clazz.new(message)
  ex.set_backtrace(caller)
  left(ex)
end

.iff(lvalue, rvalue, condition = NO_VALUE) { ... } ⇒ Either

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

Yields:

  • The condition to test (when no condition given).

Raises:

  • (ArgumentError)

    When both a condition and a block are given.

Since:

  • 1.0.0



205
206
207
208
209
# File 'lib/functional/either.rb', line 205

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) ⇒ Either

Construct a left value of either.

Since:

  • 1.0.0



102
103
104
# File 'lib/functional/either.rb', line 102

def left(value)
  new(value, true).freeze
end

.reasonEither

Construct a left value of either.



105
106
107
# File 'lib/functional/either.rb', line 105

def left(value)
  new(value, true).freeze
end

.right(value) ⇒ Either

Construct a right value of either.

Since:

  • 1.0.0



111
112
113
# File 'lib/functional/either.rb', line 111

def right(value)
  new(value, false).freeze
end

.valueEither

Construct a right value of either.



114
115
116
# File 'lib/functional/either.rb', line 114

def right(value)
  new(value, false).freeze
end

Instance Method Details

#each {|value| ... } ⇒ Enumerable Originally defined in module AbstractStruct

Yields the value of each record field in order. If no block is given an enumerator is returned.

Yield Parameters:

  • value (Object)

    the value of the given field

Since:

  • 1.0.0

#each_pair {|field, value| ... } ⇒ Enumerable Originally defined in module AbstractStruct

Yields the name and value of each record field in order. If no block is given an enumerator is returned.

Yield Parameters:

  • field (Symbol)

    the record field for the current iteration

  • value (Object)

    the value of the current field

Since:

  • 1.0.0

#either(lproc, rproc) ⇒ Object

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

Since:

  • 1.0.0



191
192
193
# File 'lib/functional/either.rb', line 191

def either(lproc, rproc)
  left? ? lproc.call(left) : rproc.call(right)
end

#eql?(other) ⇒ Boolean Also known as: == Originally defined in module AbstractStruct

Equality--Returns true if other has the same record subclass and has equal field values (according to Object#==).

Since:

  • 1.0.0

#fieldsArray Originally defined in module AbstractStruct

A frozen array of all record fields.

Since:

  • 1.0.0

#inspectString Also known as: to_s Originally defined in module AbstractStruct

Describe the contents of this struct in a string. Will include the name of the record class, all fields, and all values.

Since:

  • 1.0.0

#leftObject Also known as: reason

Projects this either as a left.

Since:

  • 1.0.0



144
145
146
# File 'lib/functional/either.rb', line 144

def left
  left? ? to_h[:left] : nil
end

#left?Boolean Also known as: reason?, rejected?

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

Since:

  • 1.0.0



160
161
162
# File 'lib/functional/either.rb', line 160

def left?
  @is_left
end

#lengthFixnum Also known as: size Originally defined in module AbstractStruct

Returns the number of record fields.

Since:

  • 1.0.0

#rightObject Also known as: value

Projects this either as a right.

Since:

  • 1.0.0



152
153
154
# File 'lib/functional/either.rb', line 152

def right
  right? ? to_h[:right] : nil
end

#right?Boolean Also known as: value?, fulfilled?

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

Since:

  • 1.0.0



169
170
171
# File 'lib/functional/either.rb', line 169

def right?
  ! left?
end

#swapEither

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

Since:

  • 1.0.0



178
179
180
181
182
183
184
# File 'lib/functional/either.rb', line 178

def swap
  if left?
    self.class.send(:new, left, false)
  else
    self.class.send(:new, right, true)
  end
end

#to_hHash Originally defined in module AbstractStruct

Returns a Hash containing the names and values for the record’s fields.

Since:

  • 1.0.0