Class: Functional::Either
- Inherits:
-
Synchronization::Object
- Object
- Synchronization::Object
- Functional::Either
- Defined in:
- lib/functional/either.rb
Overview
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.
Constant Summary
Instance Attribute Summary (collapse)
-
- (Array) values
included
from AbstractStruct
readonly
The values of all record fields in order, frozen.
Class Method Summary (collapse)
-
+ (Either) error(message = nil, clazz = StandardError)
Create an
Either
with the left value set to anException
object complete with message and backtrace. -
+ (Either) iff(lvalue, rvalue, condition = NO_VALUE) { ... }
If the condition satisfies, return the given A in left, otherwise, return the given B in right.
-
+ (Either) left(value)
Construct a left value of either.
-
+ (Either) reason
Construct a left value of either.
-
+ (Either) right(value)
Construct a right value of either.
-
+ (Either) value
Construct a right value of either.
Instance Method Summary (collapse)
-
- (Enumerable) each {|value| ... }
included
from AbstractStruct
Yields the value of each record field in order.
-
- (Enumerable) each_pair {|field, value| ... }
included
from AbstractStruct
Yields the name and value of each record field in order.
-
- (Object) either(lproc, rproc)
The catamorphism for either.
-
- (Boolean) eql?(other)
(also: #==)
included
from AbstractStruct
Equality--Returns
true
ifother
has the same record subclass and has equal field values (according toObject#==
). -
- (Array) fields
included
from AbstractStruct
A frozen array of all record fields.
-
- (String) inspect
(also: #to_s)
included
from AbstractStruct
Describe the contents of this struct in a string.
-
- (Object) left
(also: #reason)
Projects this either as a left.
-
- (Boolean) left?
(also: #reason?, #rejected?)
Returns true if this either is a left, false otherwise.
-
- (Fixnum) length
(also: #size)
included
from AbstractStruct
Returns the number of record fields.
-
- (Object) right
(also: #value)
Projects this either as a right.
-
- (Boolean) right?
(also: #value?, #fulfilled?)
Returns true if this either is a right, false otherwise.
-
- (Either) swap
If this is a left, then return the left value in right, or vice versa.
-
- (Hash) to_h
included
from AbstractStruct
Returns a Hash containing the names and values for the record’s fields.
Instance Attribute Details
- (Array) values (readonly) Originally defined in module AbstractStruct
Returns the values of all record fields in order, frozen
Class Method Details
+ (Either) error(message = nil, clazz = StandardError)
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.
133 134 135 136 137 |
# File 'lib/functional/either.rb', line 133 def error( = nil, clazz = StandardError) ex = clazz.new() ex.set_backtrace(caller) left(ex) end |
+ (Either) iff(lvalue, rvalue, condition = NO_VALUE) { ... }
If the condition satisfies, return the given A in left, otherwise, return the given B in right.
204 205 206 207 208 |
# 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 |
+ (Either) left(value)
Construct a left value of either.
101 102 103 |
# File 'lib/functional/either.rb', line 101 def left(value) new(value, true).freeze end |
+ (Either) reason
Construct a left value of either.
104 105 106 |
# File 'lib/functional/either.rb', line 104 def left(value) new(value, true).freeze end |
+ (Either) right(value)
Construct a right value of either.
110 111 112 |
# File 'lib/functional/either.rb', line 110 def right(value) new(value, false).freeze end |
+ (Either) value
Construct a right value of either.
113 114 115 |
# File 'lib/functional/either.rb', line 113 def right(value) new(value, false).freeze end |
Instance Method Details
- (Enumerable) each {|value| ... } Originally defined in module AbstractStruct
Yields the value of each record field in order. If no block is given an enumerator is returned.
- (Enumerable) each_pair {|field, value| ... } 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.
- (Object) either(lproc, rproc)
The catamorphism for either. Folds over this either breaking into left or right.
190 191 192 |
# File 'lib/functional/either.rb', line 190 def either(lproc, rproc) left? ? lproc.call(left) : rproc.call(right) end |
- (Boolean) eql?(other) 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#==
).
- (Array) fields Originally defined in module AbstractStruct
A frozen array of all record fields.
- (String) inspect 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.
- (Object) left Also known as: reason
Projects this either as a left.
143 144 145 |
# File 'lib/functional/either.rb', line 143 def left left? ? to_h[:left] : nil end |
- (Boolean) left? Also known as: reason?, rejected?
Returns true if this either is a left, false otherwise.
159 160 161 |
# File 'lib/functional/either.rb', line 159 def left? @is_left end |
- (Fixnum) length Also known as: size Originally defined in module AbstractStruct
Returns the number of record fields.
- (Object) right Also known as: value
Projects this either as a right.
151 152 153 |
# File 'lib/functional/either.rb', line 151 def right right? ? to_h[:right] : nil end |
- (Boolean) right? Also known as: value?, fulfilled?
Returns true if this either is a right, false otherwise.
168 169 170 |
# File 'lib/functional/either.rb', line 168 def right? ! left? end |
- (Either) swap
If this is a left, then return the left value in right, or vice versa.
177 178 179 180 181 182 183 |
# 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 |
- (Hash) to_h Originally defined in module AbstractStruct
Returns a Hash containing the names and values for the record’s fields.