functional-ruby 0.7.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +92 -152
- data/doc/memo.txt +192 -0
- data/doc/pattern_matching.txt +485 -0
- data/doc/protocol.txt +221 -0
- data/doc/record.txt +144 -0
- data/doc/thread_safety.txt +8 -0
- data/lib/functional.rb +48 -18
- data/lib/functional/abstract_struct.rb +161 -0
- data/lib/functional/delay.rb +117 -0
- data/lib/functional/either.rb +222 -0
- data/lib/functional/memo.rb +93 -0
- data/lib/functional/method_signature.rb +72 -0
- data/lib/functional/option.rb +209 -0
- data/lib/functional/pattern_matching.rb +117 -100
- data/lib/functional/protocol.rb +157 -0
- data/lib/functional/protocol_info.rb +193 -0
- data/lib/functional/record.rb +155 -0
- data/lib/functional/type_check.rb +112 -0
- data/lib/functional/union.rb +152 -0
- data/lib/functional/version.rb +3 -1
- data/spec/functional/abstract_struct_shared.rb +154 -0
- data/spec/functional/complex_pattern_matching_spec.rb +205 -0
- data/spec/functional/configuration_spec.rb +17 -0
- data/spec/functional/delay_spec.rb +147 -0
- data/spec/functional/either_spec.rb +237 -0
- data/spec/functional/memo_spec.rb +207 -0
- data/spec/functional/option_spec.rb +292 -0
- data/spec/functional/pattern_matching_spec.rb +279 -276
- data/spec/functional/protocol_info_spec.rb +444 -0
- data/spec/functional/protocol_spec.rb +274 -0
- data/spec/functional/record_spec.rb +175 -0
- data/spec/functional/type_check_spec.rb +103 -0
- data/spec/functional/union_spec.rb +110 -0
- data/spec/spec_helper.rb +6 -4
- metadata +55 -45
- data/lib/functional/behavior.rb +0 -138
- data/lib/functional/behaviour.rb +0 -2
- data/lib/functional/catalog.rb +0 -487
- data/lib/functional/collection.rb +0 -403
- data/lib/functional/inflect.rb +0 -127
- data/lib/functional/platform.rb +0 -120
- data/lib/functional/search.rb +0 -132
- data/lib/functional/sort.rb +0 -41
- data/lib/functional/utilities.rb +0 -189
- data/md/behavior.md +0 -188
- data/md/catalog.md +0 -32
- data/md/collection.md +0 -32
- data/md/inflect.md +0 -32
- data/md/pattern_matching.md +0 -512
- data/md/platform.md +0 -32
- data/md/search.md +0 -32
- data/md/sort.md +0 -32
- data/md/utilities.md +0 -55
- data/spec/functional/behavior_spec.rb +0 -528
- data/spec/functional/catalog_spec.rb +0 -1206
- data/spec/functional/collection_spec.rb +0 -752
- data/spec/functional/inflect_spec.rb +0 -85
- data/spec/functional/integration_spec.rb +0 -205
- data/spec/functional/platform_spec.rb +0 -501
- data/spec/functional/search_spec.rb +0 -187
- data/spec/functional/sort_spec.rb +0 -61
- data/spec/functional/utilities_spec.rb +0 -277
@@ -0,0 +1,161 @@
|
|
1
|
+
require_relative 'protocol'
|
2
|
+
|
3
|
+
Functional::SpecifyProtocol(:Struct) do
|
4
|
+
instance_method :fields
|
5
|
+
instance_method :values
|
6
|
+
instance_method :length
|
7
|
+
instance_method :each
|
8
|
+
instance_method :each_pair
|
9
|
+
end
|
10
|
+
|
11
|
+
module Functional
|
12
|
+
|
13
|
+
# An abstract base class for immutable struct classes.
|
14
|
+
module AbstractStruct
|
15
|
+
|
16
|
+
# @return [Array] the values of all record fields in order, frozen
|
17
|
+
attr_reader :values
|
18
|
+
|
19
|
+
# Yields the value of each record field in order.
|
20
|
+
# If no block is given an enumerator is returned.
|
21
|
+
#
|
22
|
+
# @yieldparam [Object] value the value of the given field
|
23
|
+
#
|
24
|
+
# @return [Enumerable] when no block is given
|
25
|
+
def each
|
26
|
+
return enum_for(:each) unless block_given?
|
27
|
+
fields.each do |field|
|
28
|
+
yield(self.send(field))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Yields the name and value of each record field in order.
|
33
|
+
# If no block is given an enumerator is returned.
|
34
|
+
#
|
35
|
+
# @yieldparam [Symbol] field the record field for the current iteration
|
36
|
+
# @yieldparam [Object] value the value of the current field
|
37
|
+
#
|
38
|
+
# @return [Enumerable] when no block is given
|
39
|
+
def each_pair
|
40
|
+
return enum_for(:each_pair) unless block_given?
|
41
|
+
fields.each do |field|
|
42
|
+
yield(field, self.send(field))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Equality--Returns `true` if `other` has the same record subclass and has equal
|
47
|
+
# field values (according to `Object#==`).
|
48
|
+
#
|
49
|
+
# @param [Object] other the other record to compare for equality
|
50
|
+
# @return [Booleab] true when equal else false
|
51
|
+
def eql?(other)
|
52
|
+
self.class == other.class && self.to_h == other.to_h
|
53
|
+
end
|
54
|
+
alias_method :==, :eql?
|
55
|
+
|
56
|
+
# @!macro [attach] inspect_method
|
57
|
+
#
|
58
|
+
# Describe the contents of this record in a string. Will include the name of the
|
59
|
+
# record class, all fields, and all values.
|
60
|
+
#
|
61
|
+
# @return [String] the class and contents of this record
|
62
|
+
def inspect
|
63
|
+
state = to_h.to_s.gsub(/^{/, '').gsub(/}$/, '')
|
64
|
+
"#<#{self.class.datatype} #{self.class} #{state}>"
|
65
|
+
end
|
66
|
+
alias_method :to_s, :inspect
|
67
|
+
|
68
|
+
# Returns the number of record fields.
|
69
|
+
#
|
70
|
+
# @return [Fixnum] the number of record fields
|
71
|
+
def length
|
72
|
+
fields.length
|
73
|
+
end
|
74
|
+
alias_method :size, :length
|
75
|
+
|
76
|
+
# A frozen array of all record fields.
|
77
|
+
#
|
78
|
+
# @return [Array] all record fields in order, frozen
|
79
|
+
def fields
|
80
|
+
self.class.fields
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a Hash containing the names and values for the record’s fields.
|
84
|
+
#
|
85
|
+
# @return [Hash] collection of all fields and their associated values
|
86
|
+
def to_h
|
87
|
+
@data
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
# Set the internal data hash to a copy of the given hash and freeze it.
|
93
|
+
# @param [Hash] data the data hash
|
94
|
+
#
|
95
|
+
# @!visibility private
|
96
|
+
def set_data_hash(data)
|
97
|
+
@data = data.dup.freeze
|
98
|
+
end
|
99
|
+
|
100
|
+
# Set the internal values array to a copy of the given array and freeze it.
|
101
|
+
# @param [Array] values the values array
|
102
|
+
#
|
103
|
+
# @!visibility private
|
104
|
+
def set_values_array(values)
|
105
|
+
@values = values.dup.freeze
|
106
|
+
end
|
107
|
+
|
108
|
+
# Define a new struct class and, if necessary, register it with
|
109
|
+
# the calling class/module. Will also set the datatype and fields
|
110
|
+
# class attributes on the new struct class.
|
111
|
+
#
|
112
|
+
# @param [Module] parent the class/module that is defining the new struct
|
113
|
+
# @param [Symbol] datatype the datatype value for the new struct class
|
114
|
+
# @param [Array] fields the list of symbolic names for all data fields
|
115
|
+
# @return [Functional::AbstractStruct, Array] the new class and the
|
116
|
+
# (possibly) updated fields array
|
117
|
+
#
|
118
|
+
# @!visibility private
|
119
|
+
def self.define_class(parent, datatype, fields)
|
120
|
+
struct = Class.new{ include AbstractStruct }
|
121
|
+
if fields.first.is_a? String
|
122
|
+
parent.const_set(fields.first, struct)
|
123
|
+
fields = fields[1, fields.length-1]
|
124
|
+
end
|
125
|
+
fields = fields.collect{|field| field.to_sym }.freeze
|
126
|
+
struct.send(:datatype=, datatype.to_sym)
|
127
|
+
struct.send(:fields=, fields)
|
128
|
+
[struct, fields]
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def self.included(base)
|
134
|
+
base.extend(ClassMethods)
|
135
|
+
super(base)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Class methods added to a class that includes {Functional::PatternMatching}
|
139
|
+
#
|
140
|
+
# @!visibility private
|
141
|
+
module ClassMethods
|
142
|
+
|
143
|
+
# A frozen Array of all record fields in order
|
144
|
+
attr_reader :fields
|
145
|
+
|
146
|
+
# A symbol describing the object's datatype
|
147
|
+
attr_reader :datatype
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
# A frozen Array of all record fields in order
|
152
|
+
attr_writer :fields
|
153
|
+
|
154
|
+
# A symbol describing the object's datatype
|
155
|
+
attr_writer :datatype
|
156
|
+
|
157
|
+
fields = [].freeze
|
158
|
+
datatype = :struct
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
# Lazy evaluation of a block yielding an immutable result. Useful for expensive
|
6
|
+
# operations that may never be needed.
|
7
|
+
#
|
8
|
+
# When a `Delay` is created its state is set to `pending`. The value and
|
9
|
+
# reason are both `nil`. The first time the `#value` method is called the
|
10
|
+
# enclosed opration will be run and the calling thread will block. Other
|
11
|
+
# threads attempting to call `#value` will block as well. Once the operation
|
12
|
+
# is complete the *value* will be set to the result of the operation or the
|
13
|
+
# *reason* will be set to the raised exception, as appropriate. All threads
|
14
|
+
# blocked on `#value` will return. Subsequent calls to `#value` will immediately
|
15
|
+
# return the cached value. The operation will only be run once. This means that
|
16
|
+
# any side effects created by the operation will only happen once as well.
|
17
|
+
#
|
18
|
+
# @see http://clojuredocs.org/clojure_core/clojure.core/delay Clojure delay
|
19
|
+
#
|
20
|
+
# @!macro thread_safe_immutable_object
|
21
|
+
class Delay
|
22
|
+
|
23
|
+
# Create a new `Delay` in the `:pending` state.
|
24
|
+
#
|
25
|
+
# @yield the delayed operation to perform
|
26
|
+
#
|
27
|
+
# @raise [ArgumentError] if no block is given
|
28
|
+
def initialize(&block)
|
29
|
+
raise ArgumentError.new('no block given') unless block_given?
|
30
|
+
@mutex = Mutex.new
|
31
|
+
@state = :pending
|
32
|
+
@task = block
|
33
|
+
end
|
34
|
+
|
35
|
+
# Current state of block processing.
|
36
|
+
#
|
37
|
+
# @return [Symbol] the current state of block processing
|
38
|
+
def state
|
39
|
+
@mutex.lock
|
40
|
+
@state
|
41
|
+
ensure
|
42
|
+
@mutex.unlock
|
43
|
+
end
|
44
|
+
|
45
|
+
# The exception raised when processing the block. Returns `nil` if the
|
46
|
+
# operation is still `:pending` or has been `:fulfilled`.
|
47
|
+
#
|
48
|
+
# @return [StandardError] the exception raised when processing the block else nil
|
49
|
+
def reason
|
50
|
+
@mutex.lock
|
51
|
+
@reason
|
52
|
+
ensure
|
53
|
+
@mutex.unlock
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the (possibly memoized) value of the delayed operation.
|
57
|
+
#
|
58
|
+
# If the state is `:pending` then the calling thread will block while the
|
59
|
+
# operation is performed. All other threads simultaneously calling `#value`
|
60
|
+
# will block as well. Once the operation is complete (either `:fulfilled` or
|
61
|
+
# `:rejected`) all waiting threads will unblock and the new value will be
|
62
|
+
# returned.
|
63
|
+
#
|
64
|
+
# If the state is not `:pending` when `#value` is called the (possibly memoized)
|
65
|
+
# value will be returned without blocking and without performing the operation
|
66
|
+
# again.
|
67
|
+
#
|
68
|
+
# @return [Object] the (possibly memoized) result of the block operation
|
69
|
+
def value
|
70
|
+
@mutex.lock
|
71
|
+
execute_task_once
|
72
|
+
@value
|
73
|
+
ensure
|
74
|
+
@mutex.unlock
|
75
|
+
end
|
76
|
+
|
77
|
+
# Has the delay been fulfilled?
|
78
|
+
# @return [Boolean]
|
79
|
+
def fulfilled?
|
80
|
+
state == :fulfilled
|
81
|
+
end
|
82
|
+
alias_method :value?, :fulfilled?
|
83
|
+
|
84
|
+
# Has the delay been rejected?
|
85
|
+
# @return [Boolean]
|
86
|
+
def rejected?
|
87
|
+
state == :rejected
|
88
|
+
end
|
89
|
+
alias_method :reason?, :rejected?
|
90
|
+
|
91
|
+
# Is delay completion still pending?
|
92
|
+
# @return [Boolean]
|
93
|
+
def pending?
|
94
|
+
state == :pending
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
# @!visibility private
|
100
|
+
#
|
101
|
+
# Execute the enclosed task then cache and return the result if
|
102
|
+
# the current state is pending. Otherwise, return the cached result.
|
103
|
+
#
|
104
|
+
# @return [Object] the result of the block operation
|
105
|
+
def execute_task_once
|
106
|
+
if @state == :pending
|
107
|
+
begin
|
108
|
+
@value = @task.call
|
109
|
+
@state = :fulfilled
|
110
|
+
rescue => ex
|
111
|
+
@reason = ex
|
112
|
+
@state = :rejected
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require_relative 'abstract_struct'
|
2
|
+
require_relative 'protocol'
|
3
|
+
|
4
|
+
Functional::SpecifyProtocol(:Either) do
|
5
|
+
instance_method :left, 0
|
6
|
+
instance_method :left?, 0
|
7
|
+
instance_method :right, 0
|
8
|
+
instance_method :right?, 0
|
9
|
+
end
|
10
|
+
|
11
|
+
module Functional
|
12
|
+
|
13
|
+
# The `Either` type represents a value of one of two possible types (a disjoint union).
|
14
|
+
# It is an immutable structure that contains one and only one value. That value can
|
15
|
+
# be stored in one of two virtual position, `left` or `right`. The position provides
|
16
|
+
# context for the encapsulated data.
|
17
|
+
#
|
18
|
+
# One of the main uses of `Either` is as a return value that can indicate either
|
19
|
+
# success or failure. Object oriented programs generally report errors through
|
20
|
+
# either state or exception handling, neither of which work well in functional
|
21
|
+
# programming. In the former case, a method is called on an object and when an
|
22
|
+
# error occurs the state of the object is updated to reflect the error. This does
|
23
|
+
# not translate well to functional programming because they eschew state and
|
24
|
+
# mutable objects. In the latter, an exception handling block provides branching
|
25
|
+
# logic when an exception is thrown. This does not translate well to functional
|
26
|
+
# programming because it eschews side effects like structured exception handling
|
27
|
+
# (and structured exception handling tends to be very expensive). `Either` provides
|
28
|
+
# a powerful and easy-to-use alternative.
|
29
|
+
#
|
30
|
+
# A function that may generate an error can choose to return an immutable `Either`
|
31
|
+
# object in which the position of the value (left or right) indicates the nature
|
32
|
+
# of the data. By convention, a `left` value indicates an error and a `right` value
|
33
|
+
# indicates success. This leaves the caller with no ambiguity regarding success or
|
34
|
+
# failure, requires no persistent state, and does not require expensive exception
|
35
|
+
# handling facilities.
|
36
|
+
#
|
37
|
+
# `Either` provides several aliases and convenience functions to facilitate these
|
38
|
+
# failure/success conventions. The `left` and `right` functions, including their
|
39
|
+
# derivatives, are mirrored by `reason` and `value`. Failure is indicated by the
|
40
|
+
# presence of a `reason` and success is indicated by the presence of a `value`.
|
41
|
+
# When an operation has failed the either is in a `rejected` state, and when an
|
42
|
+
# operation has successed the either is in a `fulfilled` state. A common convention
|
43
|
+
# is to use a Ruby `Exception` as the `reason`. The factory method `error` facilitates
|
44
|
+
# this. The semantics and conventions of `reason`, `value`, and their derivatives
|
45
|
+
# follow the conventions of the Concurrent Ruby gem.
|
46
|
+
#
|
47
|
+
# The `left`/`right` and `reason`/`value` methods are not mutually exclusive. They
|
48
|
+
# can be commingled and still result in functionally correct code. This practice
|
49
|
+
# should be avoided, however. Consistent use of either `left`/`right` or
|
50
|
+
# `reason`/`value` against each `Either` instance will result in more expressive,
|
51
|
+
# intent-revealing code.
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
#
|
55
|
+
# require 'uri'
|
56
|
+
#
|
57
|
+
# def web_host(url)
|
58
|
+
# uri = URI(url)
|
59
|
+
# if uri.scheme == 'http'
|
60
|
+
# Functional::Either.left(uri.host)
|
61
|
+
# else
|
62
|
+
# Functional::Either.right('Invalid HTTP URL')
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# good = web_host('http://www.concurrent-ruby.com')
|
67
|
+
# good.left? #=> true
|
68
|
+
# good.left #=> "www.concurrent-ruby"
|
69
|
+
# good.right #=> nil
|
70
|
+
#
|
71
|
+
# good = web_host('bogus')
|
72
|
+
# good.left? #=> false
|
73
|
+
# good.left #=> nil
|
74
|
+
# good.right #=> "Invalid HTTP URL"
|
75
|
+
#
|
76
|
+
# @see http://functionaljava.googlecode.com/svn/artifacts/3.0/javadoc/fj/data/Either.html Functional Java
|
77
|
+
# @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Either.html Haskell Data.Either
|
78
|
+
# @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Obligation.html Concurrent Ruby
|
79
|
+
#
|
80
|
+
# @!macro thread_safe_immutable_object
|
81
|
+
class Either
|
82
|
+
include AbstractStruct
|
83
|
+
|
84
|
+
self.datatype = :either
|
85
|
+
self.fields = [:left, :right].freeze
|
86
|
+
|
87
|
+
# @!visibility private
|
88
|
+
NO_VALUE = Object.new.freeze
|
89
|
+
|
90
|
+
private_class_method :new
|
91
|
+
|
92
|
+
class << self
|
93
|
+
|
94
|
+
# Construct a left value of either.
|
95
|
+
#
|
96
|
+
# @param [Object] value The value underlying the either.
|
97
|
+
# @return [Either] A new either with the given left value.
|
98
|
+
def left(value)
|
99
|
+
new(value, true).freeze
|
100
|
+
end
|
101
|
+
alias_method :reason, :left
|
102
|
+
|
103
|
+
# Construct a right value of either.
|
104
|
+
#
|
105
|
+
# @param [Object] value The value underlying the either.
|
106
|
+
# @return [Either] A new either with the given right value.
|
107
|
+
def right(value)
|
108
|
+
new(value, false).freeze
|
109
|
+
end
|
110
|
+
alias_method :value, :right
|
111
|
+
|
112
|
+
# Create an `Either` with the left value set to an `Exception` object
|
113
|
+
# complete with message and backtrace. This is a convenience method for
|
114
|
+
# supporting the reason/value convention with the reason always being
|
115
|
+
# an `Exception` object. When no exception class is given `StandardError`
|
116
|
+
# will be used. When no message is given the default message for the
|
117
|
+
# given error class will be used.
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
#
|
121
|
+
# either = Functional::Either.error("You're a bad monkey, Mojo Jojo")
|
122
|
+
# either.fulfilled? #=> false
|
123
|
+
# either.rejected? #=> true
|
124
|
+
# either.value #=> nil
|
125
|
+
# either.reason #=> #<StandardError: You're a bad monkey, Mojo Jojo>
|
126
|
+
#
|
127
|
+
# @param [String] message The message for the new error object.
|
128
|
+
# @param [Exception] clazz The class for the new error object.
|
129
|
+
# @return [Either] A new either with an error object as the left value.
|
130
|
+
def error(message = nil, clazz = StandardError)
|
131
|
+
ex = clazz.new(message)
|
132
|
+
ex.set_backtrace(caller)
|
133
|
+
left(ex)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Projects this either as a left.
|
138
|
+
#
|
139
|
+
# @return [Object] The left value or `nil` when `right`.
|
140
|
+
def left
|
141
|
+
left? ? to_h[:left] : nil
|
142
|
+
end
|
143
|
+
alias_method :reason, :left
|
144
|
+
|
145
|
+
# Projects this either as a right.
|
146
|
+
#
|
147
|
+
# @return [Object] The right value or `nil` when `left`.
|
148
|
+
def right
|
149
|
+
right? ? to_h[:right] : nil
|
150
|
+
end
|
151
|
+
alias_method :value, :right
|
152
|
+
|
153
|
+
# Returns true if this either is a left, false otherwise.
|
154
|
+
#
|
155
|
+
# @return [Boolean] `true` if this either is a left, `false` otherwise.
|
156
|
+
def left?
|
157
|
+
@is_left
|
158
|
+
end
|
159
|
+
alias_method :reason?, :left?
|
160
|
+
alias_method :rejected?, :left?
|
161
|
+
|
162
|
+
# Returns true if this either is a right, false otherwise.
|
163
|
+
#
|
164
|
+
# @return [Boolean] `true` if this either is a right, `false` otherwise.
|
165
|
+
def right?
|
166
|
+
! left?
|
167
|
+
end
|
168
|
+
alias_method :value?, :right?
|
169
|
+
alias_method :fulfilled?, :right?
|
170
|
+
|
171
|
+
# If this is a left, then return the left value in right, or vice versa.
|
172
|
+
#
|
173
|
+
# @return [Either] The value of this either swapped to the opposing side.
|
174
|
+
def swap
|
175
|
+
if left?
|
176
|
+
self.class.send(:new, left, false)
|
177
|
+
else
|
178
|
+
self.class.send(:new, right, true)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# The catamorphism for either. Folds over this either breaking into left or right.
|
183
|
+
#
|
184
|
+
# @param [Proc] lproc The function to call if this is left.
|
185
|
+
# @param [Proc] rproc The function to call if this is right.
|
186
|
+
# @return [Object] The reduced value.
|
187
|
+
def either(lproc, rproc)
|
188
|
+
left? ? lproc.call(left) : rproc.call(right)
|
189
|
+
end
|
190
|
+
|
191
|
+
# If the condition satisfies, return the given A in left, otherwise, return the given B in right.
|
192
|
+
#
|
193
|
+
# @param [Object] lvalue The left value to use if the condition satisfies.
|
194
|
+
# @param [Object] rvalue The right value to use if the condition does not satisfy.
|
195
|
+
# @param [Boolean] condition The condition to test (when no block given).
|
196
|
+
# @yield The condition to test (when no condition given).
|
197
|
+
#
|
198
|
+
# @return [Either] A constructed either based on the given condition.
|
199
|
+
#
|
200
|
+
# @raise [ArgumentError] When both a condition and a block are given.
|
201
|
+
def self.iff(lvalue, rvalue, condition = NO_VALUE)
|
202
|
+
raise ArgumentError.new('requires either a condition or a block, not both') if condition != NO_VALUE && block_given?
|
203
|
+
condition = block_given? ? yield : !! condition
|
204
|
+
condition ? left(lvalue) : right(rvalue)
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
# Create a new Either wil the given value and disposition.
|
210
|
+
#
|
211
|
+
# @param [Object] value the value of this either
|
212
|
+
# @param [Boolean] is_left is this a left either or right?
|
213
|
+
#
|
214
|
+
# @!visibility private
|
215
|
+
def initialize(value, is_left)
|
216
|
+
@is_left = is_left
|
217
|
+
hsh = is_left ? {left: value, right: nil} : {left: nil, right: value}
|
218
|
+
set_data_hash(hsh)
|
219
|
+
set_values_array(hsh.values)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|