functional-ruby 1.0.0 → 1.1.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/CHANGELOG.md +22 -0
- data/README.md +37 -17
- data/doc/record.txt +63 -0
- data/doc/thread_safety.txt +9 -0
- data/lib/functional.rb +4 -0
- data/lib/functional/abstract_struct.rb +4 -2
- data/lib/functional/delay.rb +2 -0
- data/lib/functional/either.rb +2 -0
- data/lib/functional/final_struct.rb +231 -0
- data/lib/functional/final_var.rb +163 -0
- data/lib/functional/memo.rb +4 -0
- data/lib/functional/method_signature.rb +2 -0
- data/lib/functional/option.rb +2 -0
- data/lib/functional/pattern_matching.rb +1 -0
- data/lib/functional/protocol.rb +2 -0
- data/lib/functional/protocol_info.rb +3 -0
- data/lib/functional/record.rb +4 -2
- data/lib/functional/tuple.rb +247 -0
- data/lib/functional/type_check.rb +2 -0
- data/lib/functional/union.rb +2 -0
- data/lib/functional/value_struct.rb +144 -0
- data/lib/functional/version.rb +1 -1
- data/spec/functional/final_struct_spec.rb +266 -0
- data/spec/functional/final_var_spec.rb +169 -0
- data/spec/functional/record_spec.rb +30 -1
- data/spec/functional/tuple_spec.rb +679 -0
- data/spec/functional/value_struct_spec.rb +199 -0
- metadata +19 -4
@@ -3,6 +3,8 @@ module Functional
|
|
3
3
|
# Supplies type-checking helpers whenever included.
|
4
4
|
#
|
5
5
|
# @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor/TypeCheck.html TypeCheck in Concurrent Ruby
|
6
|
+
#
|
7
|
+
# @since 1.0.0
|
6
8
|
module TypeCheck
|
7
9
|
|
8
10
|
# Performs an `is_a?` check of the given value object against the
|
data/lib/functional/union.rb
CHANGED
@@ -52,6 +52,8 @@ module Functional
|
|
52
52
|
# @see http://www.ruby-doc.org/core-2.1.2/Struct.html Ruby `Struct` class
|
53
53
|
# @see http://en.wikipedia.org/wiki/Union_type "Union type" on Wikipedia
|
54
54
|
#
|
55
|
+
# @since 1.0.0
|
56
|
+
#
|
55
57
|
# @!macro thread_safe_immutable_object
|
56
58
|
module Union
|
57
59
|
extend self
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Functional
|
2
|
+
|
3
|
+
# A variation on Ruby's `OpenStruct` in which all fields are immutable and
|
4
|
+
# set at instantiation. For compatibility with {Functional::FinalStruct},
|
5
|
+
# predicate methods exist for all potential fields and these predicates
|
6
|
+
# indicate if the field has been set. Calling a predicate method for a field
|
7
|
+
# that does not exist on the struct will return false.
|
8
|
+
#
|
9
|
+
# Unlike {Functional::Record}, which returns a new class which can be used to
|
10
|
+
# create immutable objects, `ValueStruct` creates simple immutable objects.
|
11
|
+
#
|
12
|
+
# @example Instanciation
|
13
|
+
# name = Functional::ValueStruct.new(first: 'Douglas', last: 'Adams')
|
14
|
+
#
|
15
|
+
# name.first #=> 'Douglas'
|
16
|
+
# name.last #=> 'Adams'
|
17
|
+
# name.first? #=> true
|
18
|
+
# name.last? #=> true
|
19
|
+
# name.middle? #=> false
|
20
|
+
#
|
21
|
+
# @since 1.1.0
|
22
|
+
#
|
23
|
+
# @see Functional::Record
|
24
|
+
# @see Functional::FinalStruct
|
25
|
+
# @see http://www.ruby-doc.org/stdlib-2.1.2/libdoc/ostruct/rdoc/OpenStruct.html
|
26
|
+
#
|
27
|
+
# @!macro thread_safe_immutable_object
|
28
|
+
class ValueStruct
|
29
|
+
|
30
|
+
def initialize(attributes)
|
31
|
+
raise ArgumentError.new('attributes must be given as a hash') unless attributes.respond_to?(:each_pair)
|
32
|
+
@attribute_hash = {}
|
33
|
+
attributes.each_pair do |field, value|
|
34
|
+
set_attribute(field, value)
|
35
|
+
end
|
36
|
+
@attribute_hash.freeze
|
37
|
+
self.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the value of the given field.
|
41
|
+
#
|
42
|
+
# @param [Symbol] field the field to retrieve the value for
|
43
|
+
# @return [Object] the value of the field is set else nil
|
44
|
+
def get(field)
|
45
|
+
@attribute_hash[field.to_sym]
|
46
|
+
end
|
47
|
+
alias_method :[], :get
|
48
|
+
|
49
|
+
# Check the internal hash to unambiguously verify that the given
|
50
|
+
# attribute has been set.
|
51
|
+
#
|
52
|
+
# @param [Symbol] field the field to get the value for
|
53
|
+
# @return [Boolean] true if the field has been set else false
|
54
|
+
def set?(field)
|
55
|
+
@attribute_hash.has_key?(field.to_sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the current value of the given field if already set else return the given
|
59
|
+
# default value.
|
60
|
+
#
|
61
|
+
# @param [Symbol] field the field to get the value for
|
62
|
+
# @param [Object] default the value to return if the field has not been set
|
63
|
+
# @return [Object] the value of the given field else the given default value
|
64
|
+
def fetch(field, default)
|
65
|
+
@attribute_hash.fetch(field.to_sym, default)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Calls the block once for each attribute, passing the key/value pair as parameters.
|
69
|
+
# If no block is given, an enumerator is returned instead.
|
70
|
+
#
|
71
|
+
# @yieldparam [Symbol] field the struct field for the current iteration
|
72
|
+
# @yieldparam [Object] value the value of the current field
|
73
|
+
#
|
74
|
+
# @return [Enumerable] when no block is given
|
75
|
+
def each_pair
|
76
|
+
return enum_for(:each_pair) unless block_given?
|
77
|
+
@attribute_hash.each do |field, value|
|
78
|
+
yield(field, value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Converts the `ValueStruct` to a `Hash` with keys representing each attribute
|
83
|
+
# (as symbols) and their corresponding values.
|
84
|
+
#
|
85
|
+
# @return [Hash] a `Hash` representing this struct
|
86
|
+
def to_h
|
87
|
+
@attribute_hash.dup # dup removes the frozen flag
|
88
|
+
end
|
89
|
+
|
90
|
+
# Compares this object and other for equality. A `ValueStruct` is `eql?` to
|
91
|
+
# other when other is a `ValueStruct` and the two objects have identical
|
92
|
+
# fields and values.
|
93
|
+
#
|
94
|
+
# @param [Object] other the other record to compare for equality
|
95
|
+
# @return [Boolean] true when equal else false
|
96
|
+
def eql?(other)
|
97
|
+
other.is_a?(self.class) && @attribute_hash == other.to_h
|
98
|
+
end
|
99
|
+
alias_method :==, :eql?
|
100
|
+
|
101
|
+
# Describe the contents of this object in a string.
|
102
|
+
#
|
103
|
+
# @return [String] the string representation of this object
|
104
|
+
#
|
105
|
+
# @!visibility private
|
106
|
+
def inspect
|
107
|
+
state = @attribute_hash.to_s.gsub(/^{/, '').gsub(/}$/, '')
|
108
|
+
"#<#{self.class} #{state}>"
|
109
|
+
end
|
110
|
+
alias_method :to_s, :inspect
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
# Set the value of the give field to the given value.
|
115
|
+
#
|
116
|
+
# @param [Symbol] field the field to set the value for
|
117
|
+
# @param [Object] value the value to set the field to
|
118
|
+
# @return [Object] the final value of the given field
|
119
|
+
#
|
120
|
+
# @!visibility private
|
121
|
+
def set_attribute(field, value)
|
122
|
+
@attribute_hash[field.to_sym] = value
|
123
|
+
end
|
124
|
+
|
125
|
+
# Check the method name and args for signatures matching potential
|
126
|
+
# final predicate methods. If the signature matches call the appropriate
|
127
|
+
# method
|
128
|
+
#
|
129
|
+
# @param [Symbol] symbol the name of the called function
|
130
|
+
# @param [Array] args zero or more arguments
|
131
|
+
# @return [Object] the result of the proxied method or the `super` call
|
132
|
+
#
|
133
|
+
# @!visibility private
|
134
|
+
def method_missing(symbol, *args)
|
135
|
+
if args.length == 0 && (match = /([^\?]+)\?$/.match(symbol))
|
136
|
+
set?(match[1])
|
137
|
+
elsif args.length == 0 && set?(symbol)
|
138
|
+
get(symbol)
|
139
|
+
else
|
140
|
+
super
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/functional/version.rb
CHANGED
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Functional
|
5
|
+
|
6
|
+
describe FinalStruct do
|
7
|
+
|
8
|
+
context 'instanciation' do
|
9
|
+
|
10
|
+
specify 'with no args defines no fields' do
|
11
|
+
subject = FinalStruct.new
|
12
|
+
expect(subject.to_h).to be_empty
|
13
|
+
end
|
14
|
+
|
15
|
+
specify 'with a hash sets fields using has values' do
|
16
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
17
|
+
expect(subject.foo).to eq 1
|
18
|
+
expect(subject.bar).to eq :two
|
19
|
+
expect(subject.baz).to eq 'three'
|
20
|
+
end
|
21
|
+
|
22
|
+
specify 'with a hash creates true predicates for has keys' do
|
23
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
24
|
+
expect(subject.foo?).to be true
|
25
|
+
expect(subject.bar?).to be true
|
26
|
+
expect(subject.baz?).to be true
|
27
|
+
end
|
28
|
+
|
29
|
+
specify 'can be created from any object that responds to #to_h' do
|
30
|
+
clazz = Class.new do
|
31
|
+
def to_h; {answer: 42, harmless: 'mostly'}; end
|
32
|
+
end
|
33
|
+
struct = clazz.new
|
34
|
+
subject = FinalStruct.new(struct)
|
35
|
+
expect(subject.answer).to eq 42
|
36
|
+
expect(subject.harmless).to eq 'mostly'
|
37
|
+
end
|
38
|
+
|
39
|
+
specify 'raises an exception if given a non-hash argument' do
|
40
|
+
expect {
|
41
|
+
FinalStruct.new(:bogus)
|
42
|
+
}.to raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'set fields' do
|
47
|
+
|
48
|
+
subject do
|
49
|
+
struct = FinalStruct.new
|
50
|
+
struct.foo = 42
|
51
|
+
struct.bar = "Don't Panic"
|
52
|
+
struct
|
53
|
+
end
|
54
|
+
|
55
|
+
specify 'have a reader which returns the value' do
|
56
|
+
expect(subject.foo).to eq 42
|
57
|
+
expect(subject.bar).to eq "Don't Panic"
|
58
|
+
end
|
59
|
+
|
60
|
+
specify 'have a predicate which returns true' do
|
61
|
+
expect(subject.foo?).to be true
|
62
|
+
expect(subject.bar?).to be true
|
63
|
+
end
|
64
|
+
|
65
|
+
specify 'raise an exception when written to again' do
|
66
|
+
expect {subject.foo = 0}.to raise_error(Functional::FinalityError)
|
67
|
+
expect {subject.bar = 0}.to raise_error(Functional::FinalityError)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'unset fields' do
|
72
|
+
|
73
|
+
subject { FinalStruct.new }
|
74
|
+
|
75
|
+
specify 'have a magic reader that always returns nil' do
|
76
|
+
expect(subject.foo).to be nil
|
77
|
+
expect(subject.bar).to be nil
|
78
|
+
expect(subject.baz).to be nil
|
79
|
+
end
|
80
|
+
|
81
|
+
specify 'have a magic predicate that always returns false' do
|
82
|
+
expect(subject.foo?).to be false
|
83
|
+
expect(subject.bar?).to be false
|
84
|
+
expect(subject.baz?).to be false
|
85
|
+
end
|
86
|
+
|
87
|
+
specify 'have a magic writer that sets the field' do
|
88
|
+
expect(subject.foo = 42).to eq 42
|
89
|
+
expect(subject.bar = :towel).to eq :towel
|
90
|
+
expect(subject.baz = "Don't Panic").to eq "Don't Panic"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'accessors' do
|
95
|
+
|
96
|
+
let!(:field_value_pairs) { {foo: 1, bar: :two, baz: 'three'} }
|
97
|
+
|
98
|
+
subject { FinalStruct.new(field_value_pairs) }
|
99
|
+
|
100
|
+
specify '#get returns the value of a set field' do
|
101
|
+
expect(subject.get(:foo)).to eq 1
|
102
|
+
end
|
103
|
+
|
104
|
+
specify '#get returns nil for an unset field' do
|
105
|
+
expect(subject.get(:bogus)).to be nil
|
106
|
+
end
|
107
|
+
|
108
|
+
specify '#[] is an alias for #get' do
|
109
|
+
expect(subject[:foo]).to eq 1
|
110
|
+
expect(subject[:bogus]).to be nil
|
111
|
+
end
|
112
|
+
|
113
|
+
specify '#set sets the value of an unset field' do
|
114
|
+
subject.set(:harmless, 'mostly')
|
115
|
+
expect(subject.harmless).to eq 'mostly'
|
116
|
+
expect(subject.harmless?).to be true
|
117
|
+
end
|
118
|
+
|
119
|
+
specify '#set raises an exception if the field has already been set' do
|
120
|
+
subject.set(:harmless, 'mostly')
|
121
|
+
expect {
|
122
|
+
subject.set(:harmless, 'extremely')
|
123
|
+
}.to raise_error(Functional::FinalityError)
|
124
|
+
end
|
125
|
+
|
126
|
+
specify '#[]= is an alias for set' do
|
127
|
+
subject[:harmless] = 'mostly'
|
128
|
+
expect(subject.harmless).to eq 'mostly'
|
129
|
+
expect {
|
130
|
+
subject[:harmless] = 'extremely'
|
131
|
+
}.to raise_error(Functional::FinalityError)
|
132
|
+
end
|
133
|
+
|
134
|
+
specify '#set? returns false for an unset field' do
|
135
|
+
expect(subject.set?(:harmless)).to be false
|
136
|
+
end
|
137
|
+
|
138
|
+
specify '#set? returns true for a field that has been set' do
|
139
|
+
subject.set(:harmless, 'mostly')
|
140
|
+
expect(subject.set?(:harmless)).to be true
|
141
|
+
end
|
142
|
+
|
143
|
+
specify '#get_or_set returns the value of a set field' do
|
144
|
+
subject.answer = 42
|
145
|
+
expect(subject.get_or_set(:answer, 100)).to eq 42
|
146
|
+
end
|
147
|
+
|
148
|
+
specify '#get_or_set sets the value of an unset field' do
|
149
|
+
subject.get_or_set(:answer, 42)
|
150
|
+
expect(subject.answer).to eq 42
|
151
|
+
expect(subject.answer?).to be true
|
152
|
+
end
|
153
|
+
|
154
|
+
specify '#get_or_set returns the value of a newly set field' do
|
155
|
+
expect(subject.get_or_set(:answer, 42)).to eq 42
|
156
|
+
end
|
157
|
+
|
158
|
+
specify '#fetch gets the value of a set field' do
|
159
|
+
subject.harmless = 'mostly'
|
160
|
+
expect(subject.fetch(:harmless, 'extremely')).to eq 'mostly'
|
161
|
+
end
|
162
|
+
|
163
|
+
specify '#fetch returns the given value when the field is unset' do
|
164
|
+
expect(subject.fetch(:harmless, 'extremely')).to eq 'extremely'
|
165
|
+
end
|
166
|
+
|
167
|
+
specify '#fetch does not set an unset field' do
|
168
|
+
subject.fetch(:answer, 42)
|
169
|
+
expect(subject.answer).to be_nil
|
170
|
+
expect(subject.answer?).to be false
|
171
|
+
end
|
172
|
+
|
173
|
+
specify '#to_h returns the key/value pairs for all set values' do
|
174
|
+
subject = FinalStruct.new(field_value_pairs)
|
175
|
+
expect(subject.to_h).to eq field_value_pairs
|
176
|
+
end
|
177
|
+
|
178
|
+
specify '#to_h is updated when new fields are added' do
|
179
|
+
subject = FinalStruct.new
|
180
|
+
field_value_pairs.each_pair do |field, value|
|
181
|
+
subject.set(field, value)
|
182
|
+
end
|
183
|
+
expect(subject.to_h).to eq field_value_pairs
|
184
|
+
end
|
185
|
+
|
186
|
+
specify '#each_pair returns an Enumerable when no block given' do
|
187
|
+
subject = FinalStruct.new(field_value_pairs)
|
188
|
+
expect(subject.each_pair).to be_a Enumerable
|
189
|
+
end
|
190
|
+
|
191
|
+
specify '#each_pair enumerates over each field/value pair' do
|
192
|
+
subject = FinalStruct.new(field_value_pairs)
|
193
|
+
result = {}
|
194
|
+
|
195
|
+
subject.each_pair do |field, value|
|
196
|
+
result[field] = value
|
197
|
+
end
|
198
|
+
|
199
|
+
expect(result).to eq field_value_pairs
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'reflection' do
|
204
|
+
|
205
|
+
specify '#eql? returns true when both define the same fields with the same values' do
|
206
|
+
first = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
207
|
+
second = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
208
|
+
|
209
|
+
expect(first.eql?(second)).to be true
|
210
|
+
expect(first == second).to be true
|
211
|
+
end
|
212
|
+
|
213
|
+
specify '#eql? returns false when other has different fields defined' do
|
214
|
+
first = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
215
|
+
second = FinalStruct.new(foo: 1, 'bar' => :two)
|
216
|
+
|
217
|
+
expect(first.eql?(second)).to be false
|
218
|
+
expect(first == second).to be false
|
219
|
+
end
|
220
|
+
|
221
|
+
specify '#eql? returns false when other has different field values' do
|
222
|
+
first = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
223
|
+
second = FinalStruct.new(foo: 1, 'bar' => :two, baz: 3)
|
224
|
+
|
225
|
+
expect(first.eql?(second)).to be false
|
226
|
+
expect(first == second).to be false
|
227
|
+
end
|
228
|
+
|
229
|
+
specify '#eql? returns false when other is not a FinalStruct' do
|
230
|
+
attributes = {answer: 42, harmless: 'mostly'}
|
231
|
+
clazz = Class.new do
|
232
|
+
def to_h; {answer: 42, harmless: 'mostly'}; end
|
233
|
+
end
|
234
|
+
other = clazz.new
|
235
|
+
subject = FinalStruct.new(attributes)
|
236
|
+
expect(subject.eql?(other)).to be false
|
237
|
+
expect(subject == other).to be false
|
238
|
+
end
|
239
|
+
|
240
|
+
specify '#inspect begins with the class name' do
|
241
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
242
|
+
expect(subject.inspect).to match(/^#<#{described_class}\s+/)
|
243
|
+
end
|
244
|
+
|
245
|
+
specify '#inspect includes all field/value pairs' do
|
246
|
+
field_value_pairs = {foo: 1, 'bar' => :two, baz: 'three'}
|
247
|
+
subject = FinalStruct.new(field_value_pairs)
|
248
|
+
|
249
|
+
field_value_pairs.each do |field, value|
|
250
|
+
expect(subject.inspect).to match(/:#{field}=>"?:?#{value}"?/)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
specify '#to_s returns the same value as #inspect' do
|
255
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
256
|
+
expect(subject.to_s).to eq subject.inspect
|
257
|
+
end
|
258
|
+
|
259
|
+
specify '#method_missing raises an exception for methods with unrecognized signatures' do
|
260
|
+
expect {
|
261
|
+
subject.foo(1, 2, 3)
|
262
|
+
}.to raise_error(NoMethodError)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
describe FinalVar do
|
6
|
+
|
7
|
+
context 'instanciation' do
|
8
|
+
|
9
|
+
it 'is unset when no arguments given' do
|
10
|
+
expect(FinalVar.new).to_not be_set
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'is set with the given argument' do
|
14
|
+
expect(FinalVar.new(41)).to be_set
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#get' do
|
19
|
+
|
20
|
+
subject { FinalVar.new }
|
21
|
+
|
22
|
+
it 'returns nil when unset' do
|
23
|
+
expect(subject.get).to be nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns the value when set' do
|
27
|
+
expect(FinalVar.new(42).get).to eq 42
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'is aliased as #value' do
|
31
|
+
expect(subject.value).to be nil
|
32
|
+
subject.set(42)
|
33
|
+
expect(subject.value).to eq 42
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context '#set' do
|
38
|
+
|
39
|
+
subject { FinalVar.new }
|
40
|
+
|
41
|
+
it 'sets the value when unset' do
|
42
|
+
subject.set(42)
|
43
|
+
expect(subject.get).to eq 42
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns the new value when unset' do
|
47
|
+
expect(subject.set(42)).to eq 42
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'raises an exception when already set' do
|
51
|
+
subject.set(42)
|
52
|
+
expect {
|
53
|
+
subject.set(42)
|
54
|
+
}.to raise_error(Functional::FinalityError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'is aliased as #value=' do
|
58
|
+
subject.value = 42
|
59
|
+
expect(subject.get).to eq 42
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context '#set?' do
|
64
|
+
|
65
|
+
it 'returns false when unset' do
|
66
|
+
expect(FinalVar.new).to_not be_set
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns true when set' do
|
70
|
+
expect(FinalVar.new(42)).to be_set
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'is aliased as value?' do
|
74
|
+
expect(FinalVar.new.value?).to be false
|
75
|
+
expect(FinalVar.new(42).value?).to be true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context '#get_or_set' do
|
80
|
+
|
81
|
+
it 'sets the value when unset' do
|
82
|
+
subject = FinalVar.new
|
83
|
+
subject.get_or_set(42)
|
84
|
+
expect(subject.get).to eq 42
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns the new value when previously unset' do
|
88
|
+
subject = FinalVar.new
|
89
|
+
expect(subject.get_or_set(42)).to eq 42
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns the current value when already set' do
|
93
|
+
subject = FinalVar.new(100)
|
94
|
+
expect(subject.get_or_set(42)).to eq 100
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context '#fetch' do
|
99
|
+
|
100
|
+
it 'returns the given default value when unset' do
|
101
|
+
subject = FinalVar.new
|
102
|
+
expect(subject.fetch(42)).to eq 42
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'does not change the current value when unset' do
|
106
|
+
subject = FinalVar.new
|
107
|
+
subject.fetch(42)
|
108
|
+
expect(subject.get).to be nil
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns the current value when already set' do
|
112
|
+
subject = FinalVar.new(100)
|
113
|
+
expect(subject.get_or_set(42)).to eq 100
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'reflection' do
|
118
|
+
|
119
|
+
specify '#eql? returns false when unset' do
|
120
|
+
expect(FinalVar.new.eql?(nil)).to be false
|
121
|
+
expect(FinalVar.new.eql?(42)).to be false
|
122
|
+
expect(FinalVar.new.eql?(FinalVar.new.value)).to be false
|
123
|
+
end
|
124
|
+
|
125
|
+
specify '#eql? returns false when set and the value does not match other' do
|
126
|
+
subject = FinalVar.new(42)
|
127
|
+
expect(subject.eql?(100)).to be false
|
128
|
+
end
|
129
|
+
|
130
|
+
specify '#eql? returns true when set and the value matches other' do
|
131
|
+
subject = FinalVar.new(42)
|
132
|
+
expect(subject.eql?(42)).to be true
|
133
|
+
end
|
134
|
+
|
135
|
+
specify '#eql? returns true when set and other is a FinalVar with the same value' do
|
136
|
+
subject = FinalVar.new(42)
|
137
|
+
other = FinalVar.new(42)
|
138
|
+
expect(subject.eql?(other)).to be true
|
139
|
+
end
|
140
|
+
|
141
|
+
specify 'aliases #== as #eql?' do
|
142
|
+
expect(FinalVar.new == nil).to be false
|
143
|
+
expect(FinalVar.new == 42).to be false
|
144
|
+
expect(FinalVar.new == FinalVar.new).to be false
|
145
|
+
expect(FinalVar.new(42) == 42).to be true
|
146
|
+
expect(FinalVar.new(42) == FinalVar.new(42)).to be true
|
147
|
+
end
|
148
|
+
|
149
|
+
specify '#inspect includes the word "value" and the value when set' do
|
150
|
+
subject = FinalVar.new(42)
|
151
|
+
expect(subject.inspect).to match(/value\s?=\s?42\s*>$/)
|
152
|
+
end
|
153
|
+
|
154
|
+
specify '#inspect include the word "unset" when unset' do
|
155
|
+
subject = FinalVar.new
|
156
|
+
expect(subject.inspect).to match(/unset\s*>$/i)
|
157
|
+
end
|
158
|
+
|
159
|
+
specify '#to_s returns nil as a string when unset' do
|
160
|
+
expect(FinalVar.new.to_s).to eq nil.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
specify '#to_s returns the value as a string when set' do
|
164
|
+
expect(FinalVar.new(42).to_s).to eq 42.to_s
|
165
|
+
expect(FinalVar.new('42').to_s).to eq '42'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|