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,292 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'abstract_struct_shared'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Functional
|
6
|
+
|
7
|
+
describe Option do
|
8
|
+
|
9
|
+
let!(:value){ 42 }
|
10
|
+
|
11
|
+
let!(:expected_fields){ [:some] }
|
12
|
+
let!(:expected_values){ [value] }
|
13
|
+
|
14
|
+
let(:struct_class) { Option }
|
15
|
+
let(:struct_object) { Option.some(value) }
|
16
|
+
let(:other_object) { Option.some(Object.new) }
|
17
|
+
|
18
|
+
let(:some_subject){ Option.some(value) }
|
19
|
+
let(:none_subject){ Option.none }
|
20
|
+
|
21
|
+
it_should_behave_like :abstract_struct
|
22
|
+
|
23
|
+
specify{ Functional::Protocol::Satisfy! Option, :Option }
|
24
|
+
specify{ Functional::Protocol::Satisfy! Option, :Disposition }
|
25
|
+
|
26
|
+
let(:some_value){ SecureRandom.uuid }
|
27
|
+
let(:other_value){ SecureRandom.uuid }
|
28
|
+
|
29
|
+
context 'initialization' do
|
30
|
+
|
31
|
+
it 'cannot be constructed directly' do
|
32
|
+
expect {
|
33
|
+
Option.new
|
34
|
+
}.to raise_error(NameError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'sets the value when constructed by #some' do
|
38
|
+
expect(Option.some(value).some).to eq value
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'sets the value to nil when constructed by #none' do
|
42
|
+
expect(Option.none.some).to be_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'sets the reason to nil when constructed by #none' do
|
46
|
+
expect(Option.none.reason).to be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'sets the optional reason when constructed by #none' do
|
50
|
+
reason = 'foobar'
|
51
|
+
expect(Option.none(reason).reason).to eq reason
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'freezes the new object' do
|
55
|
+
expect(Option.some(:foo)).to be_frozen
|
56
|
+
expect(Option.none).to be_frozen
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'state' do
|
61
|
+
|
62
|
+
specify '#some? returns true when the some value is set' do
|
63
|
+
expect(some_subject).to be_some
|
64
|
+
end
|
65
|
+
|
66
|
+
specify '#some? returns false when none' do
|
67
|
+
expect(none_subject).to_not be_some
|
68
|
+
end
|
69
|
+
|
70
|
+
specify '#none? returns true when none' do
|
71
|
+
expect(none_subject).to be_none
|
72
|
+
end
|
73
|
+
|
74
|
+
specify '#none? returns false when the some value is set' do
|
75
|
+
expect(some_subject).to_not be_none
|
76
|
+
end
|
77
|
+
|
78
|
+
specify '#some returns the some value when some is set' do
|
79
|
+
expect(some_subject.some).to eq value
|
80
|
+
end
|
81
|
+
|
82
|
+
specify '#some returns nil when none is set' do
|
83
|
+
expect(none_subject.some).to be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'aliases #some? as #fulfilled?' do
|
87
|
+
expect(some_subject).to be_fulfilled
|
88
|
+
expect(none_subject).to_not be_fulfilled
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'aliases #some? as #value?' do
|
92
|
+
expect(some_subject).to be_value
|
93
|
+
expect(none_subject).to_not be_value
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'aliases #none? as #rejected?' do
|
97
|
+
expect(some_subject).to_not be_rejected
|
98
|
+
expect(none_subject).to be_rejected
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'aliases #none? as #reason?' do
|
102
|
+
expect(some_subject).to_not be_reason
|
103
|
+
expect(none_subject).to be_reason
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'aliases #some as #value' do
|
107
|
+
expect(some_subject.value).to eq value
|
108
|
+
expect(none_subject.value).to be_nil
|
109
|
+
end
|
110
|
+
|
111
|
+
specify '#reason returns nil when some' do
|
112
|
+
expect(some_subject.reason).to be_nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'length' do
|
117
|
+
|
118
|
+
it 'returns 1 when some' do
|
119
|
+
expect(Option.some(:foo).length).to eq 1
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'returns 0 when none' do
|
123
|
+
expect(Option.none.length).to eq 0
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'as aliased as #size' do
|
127
|
+
expect(Option.some(:foo).size).to eq 1
|
128
|
+
expect(Option.none.size).to eq 0
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context '#and' do
|
133
|
+
|
134
|
+
it 'returns false when none' do
|
135
|
+
expect(Option.none.and(true)).to be false
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'returns true when some and other is a some Option' do
|
139
|
+
other = Option.some(42)
|
140
|
+
expect(Option.some(:foo).and(other)).to be true
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'returns false when some and other is a none Option' do
|
144
|
+
other = Option.none
|
145
|
+
expect(Option.some(:foo).and(other)).to be false
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'passes the value to the given block when some' do
|
149
|
+
expected = false
|
150
|
+
other = ->(some){ expected = some }
|
151
|
+
Option.some(42).and(&other)
|
152
|
+
expect(expected).to eq 42
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'returns true when some and the block returns a truthy value' do
|
156
|
+
other = ->(some){ 'truthy' }
|
157
|
+
expect(Option.some(42).and(&other)).to be true
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'returns false when some and the block returns a falsey value' do
|
161
|
+
other = ->(some){ nil }
|
162
|
+
expect(Option.some(42).and(&other)).to be false
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'returns true when some and given a truthy value' do
|
166
|
+
expect(Option.some(42).and('truthy')).to be true
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'returns false when some and given a falsey value' do
|
170
|
+
expect(Option.some(42).and(nil)).to be false
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'raises an exception when given both a value and a block' do
|
174
|
+
expect {
|
175
|
+
Option.some(42).and(:foo){|some| :bar }
|
176
|
+
}.to raise_error(ArgumentError)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context '#or' do
|
181
|
+
|
182
|
+
it 'returns true when some' do
|
183
|
+
expect(Option.some(42).or(nil)).to be true
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'returns true when none and other is a some Option' do
|
187
|
+
other = Option.some(42)
|
188
|
+
expect(Option.none.or(other)).to be true
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'returns false when none and other is a none Option' do
|
192
|
+
other = Option.none
|
193
|
+
expect(Option.none.or(other)).to be false
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'returns true when none and the block returns a truthy value' do
|
197
|
+
other = ->{ 42 }
|
198
|
+
expect(Option.none.or(&other)).to be true
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'returns false when none and the block returns a falsey value' do
|
202
|
+
other = ->{ false }
|
203
|
+
expect(Option.none.or(&other)).to be false
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'returns true when none and given a truthy value' do
|
207
|
+
expect(Option.none.or('truthy')).to be true
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'returns false when none and given a falsey value' do
|
211
|
+
expect(Option.none.or(nil)).to be false
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'raises an exception when given both a value and a block' do
|
215
|
+
expect {
|
216
|
+
Option.none.and(:foo){ :bar }
|
217
|
+
}.to raise_error(ArgumentError)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context '#else' do
|
222
|
+
|
223
|
+
it 'returns the value when some' do
|
224
|
+
expect(Option.some(some_value).else(other_value)).to eq some_value
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'returns the given value when none' do
|
228
|
+
expect(Option.none.else(other_value)).to eq other_value
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'returns the other value when none and given a some Option' do
|
232
|
+
other = Option.some(other_value)
|
233
|
+
expect(Option.none.else(other)).to eq other_value
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'returns nil when none and given a none Option' do
|
237
|
+
other = Option.none
|
238
|
+
expect(Option.none.else(other)).to be_nil
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'returns the result of the given block when none' do
|
242
|
+
other = ->{ other_value }
|
243
|
+
expect(Option.none.else(&other)).to eq other_value
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'raises an exception when given both a value and a block' do
|
247
|
+
expect {
|
248
|
+
Option.none.else(:foo){ :bar }
|
249
|
+
}.to raise_error(ArgumentError)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context '#iff' do
|
254
|
+
|
255
|
+
it 'returns a some option with the given value when the boolean is true' do
|
256
|
+
subject = Option.iff(:foo, true)
|
257
|
+
expect(subject).to be_some
|
258
|
+
expect(subject.some).to eq :foo
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'returns a none option when the boolean is false' do
|
262
|
+
subject = Option.iff(:foo, false)
|
263
|
+
expect(subject).to be_none
|
264
|
+
expect(subject.some).to be_nil
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'returns a some option with the given value when the block is truthy' do
|
268
|
+
subject = Option.iff(:foo){ :baz }
|
269
|
+
expect(subject).to be_some
|
270
|
+
expect(subject.some).to eq :foo
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'returns a none option when the block is false' do
|
274
|
+
subject = Option.iff(:foo){ false }
|
275
|
+
expect(subject).to be_none
|
276
|
+
expect(subject.some).to be_nil
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'returns a none option when the block is nil' do
|
280
|
+
subject = Option.iff(:foo){ nil }
|
281
|
+
expect(subject).to be_none
|
282
|
+
expect(subject.some).to be_nil
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'raises an exception when both a boolean and a block are given' do
|
286
|
+
expect {
|
287
|
+
subject = Option.iff(:foo, true){ nil }
|
288
|
+
}.to raise_error(ArgumentError)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
@@ -1,416 +1,419 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'ostruct'
|
3
3
|
|
4
|
-
|
4
|
+
module Functional
|
5
5
|
|
6
|
-
|
7
|
-
clazz = Class.new
|
8
|
-
clazz.send(:include, PatternMatching)
|
9
|
-
clazz.instance_eval(&block) if block_given?
|
10
|
-
return clazz
|
11
|
-
end
|
6
|
+
describe PatternMatching do
|
12
7
|
|
13
|
-
|
8
|
+
def new_clazz(&block)
|
9
|
+
clazz = Class.new
|
10
|
+
clazz.send(:include, PatternMatching)
|
11
|
+
clazz.instance_eval(&block) if block_given?
|
12
|
+
return clazz
|
13
|
+
end
|
14
14
|
|
15
|
-
|
15
|
+
subject { new_clazz }
|
16
16
|
|
17
|
-
|
18
|
-
lambda {
|
19
|
-
class Clazz
|
20
|
-
include PatternMatching
|
21
|
-
defn(:foo){}
|
22
|
-
end
|
23
|
-
}.should_not raise_error
|
24
|
-
end
|
17
|
+
context '#defn declaration' do
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
it 'can be used within a class declaration' do
|
20
|
+
expect {
|
21
|
+
class Clazz
|
22
|
+
include PatternMatching
|
23
|
+
defn(:foo){}
|
24
|
+
end
|
25
|
+
}.not_to raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can be used on a class object' do
|
29
|
+
expect {
|
30
|
+
clazz = Class.new
|
31
|
+
clazz.send(:include, PatternMatching)
|
32
|
+
clazz.defn(:foo){}
|
33
|
+
}.not_to raise_error
|
34
|
+
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
it 'requires a block' do
|
37
|
+
expect {
|
38
|
+
clazz = Class.new
|
39
|
+
clazz.send(:include, PatternMatching)
|
40
|
+
clazz.defn(:foo)
|
41
|
+
}.to raise_error(ArgumentError)
|
42
|
+
end
|
40
43
|
end
|
41
|
-
end
|
42
44
|
|
43
|
-
|
45
|
+
context 'constructor' do
|
44
46
|
|
45
|
-
|
47
|
+
it 'can pattern match the constructor' do
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
unless RUBY_VERSION == '1.9.2'
|
50
|
+
subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'three args' }
|
51
|
+
subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'two args' }
|
52
|
+
subject.defn(:initialize, PatternMatching::UNBOUND) { 'one arg' }
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
expect { subject.new(1) }.not_to raise_error
|
55
|
+
expect { subject.new(1, 2) }.not_to raise_error
|
56
|
+
expect { subject.new(1, 2, 3) }.not_to raise_error
|
57
|
+
expect { subject.new(1, 2, 3, 4) }.to raise_error
|
58
|
+
end
|
56
59
|
end
|
57
60
|
end
|
58
|
-
end
|
59
61
|
|
60
|
-
|
62
|
+
context 'parameter count' do
|
61
63
|
|
62
|
-
|
64
|
+
it 'does not match a call with not enough arguments' do
|
63
65
|
|
64
|
-
|
66
|
+
subject.defn(:foo, true) { 'expected' }
|
65
67
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
expect {
|
69
|
+
subject.new.foo()
|
70
|
+
}.to raise_error(NoMethodError)
|
71
|
+
end
|
70
72
|
|
71
|
-
|
73
|
+
it 'does not match a call with too many arguments' do
|
72
74
|
|
73
|
-
|
75
|
+
subject.defn(:foo, true) { 'expected' }
|
76
|
+
|
77
|
+
expect {
|
78
|
+
subject.new.foo(true, false)
|
79
|
+
}.to raise_error(NoMethodError)
|
80
|
+
end
|
74
81
|
|
75
|
-
lambda {
|
76
|
-
subject.new.foo(true, false)
|
77
|
-
}.should raise_error(NoMethodError)
|
78
82
|
end
|
79
83
|
|
80
|
-
|
84
|
+
context 'recursion' do
|
81
85
|
|
82
|
-
|
86
|
+
it 'defers unmatched calls to the superclass' do
|
83
87
|
|
84
|
-
|
88
|
+
class UnmatchedCallTesterSuperclass
|
89
|
+
def foo(bar)
|
90
|
+
return bar
|
91
|
+
end
|
92
|
+
end
|
85
93
|
|
86
|
-
|
87
|
-
|
88
|
-
|
94
|
+
class UnmatchedCallTesterSubclass < UnmatchedCallTesterSuperclass
|
95
|
+
include PatternMatching
|
96
|
+
defn(:foo) { 'baz' }
|
89
97
|
end
|
90
|
-
end
|
91
98
|
|
92
|
-
|
93
|
-
|
94
|
-
defn(:foo) { 'baz' }
|
99
|
+
subject = UnmatchedCallTesterSubclass.new
|
100
|
+
expect(subject.foo(:bar)).to eq :bar
|
95
101
|
end
|
96
102
|
|
97
|
-
|
98
|
-
subject.foo(:bar).should eq :bar
|
99
|
-
end
|
103
|
+
it 'can call another match from within a match' do
|
100
104
|
|
101
|
-
|
105
|
+
subject.defn(:foo, :bar) { |arg| foo(:baz) }
|
106
|
+
subject.defn(:foo, :baz) { |arg| 'expected' }
|
102
107
|
|
103
|
-
|
104
|
-
|
108
|
+
expect(subject.new.foo(:bar)).to eq 'expected'
|
109
|
+
end
|
105
110
|
|
106
|
-
|
107
|
-
end
|
111
|
+
it 'can call a superclass method from within a match' do
|
108
112
|
|
109
|
-
|
113
|
+
class RecursiveCallTesterSuperclass
|
114
|
+
def foo(bar)
|
115
|
+
return bar
|
116
|
+
end
|
117
|
+
end
|
110
118
|
|
111
|
-
|
112
|
-
|
113
|
-
|
119
|
+
class RecursiveCallTesterSubclass < RecursiveCallTesterSuperclass
|
120
|
+
include PatternMatching
|
121
|
+
defn(:foo, :bar) { foo(:baz) }
|
114
122
|
end
|
115
|
-
end
|
116
123
|
|
117
|
-
|
118
|
-
|
119
|
-
defn(:foo, :bar) { foo(:baz) }
|
124
|
+
subject = RecursiveCallTesterSubclass.new
|
125
|
+
expect(subject.foo(:bar)).to eq :baz
|
120
126
|
end
|
121
|
-
|
122
|
-
subject = RecursiveCallTesterSubclass.new
|
123
|
-
subject.foo(:bar).should eq :baz
|
124
127
|
end
|
125
|
-
end
|
126
128
|
|
127
|
-
|
129
|
+
context 'datatypes' do
|
128
130
|
|
129
|
-
|
131
|
+
it 'matches an argument of the class given in the match parameter' do
|
130
132
|
|
131
|
-
|
132
|
-
|
133
|
+
subject.defn(:foo, Integer) { 'expected' }
|
134
|
+
expect(subject.new.foo(100)).to eq 'expected'
|
133
135
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
136
|
+
expect {
|
137
|
+
subject.new.foo('hello')
|
138
|
+
}.to raise_error(NoMethodError)
|
139
|
+
end
|
138
140
|
|
139
|
-
|
141
|
+
it 'passes the matched argument to the block' do
|
140
142
|
|
141
|
-
|
142
|
-
|
143
|
+
subject.defn(:foo, Integer) { |arg| arg }
|
144
|
+
expect(subject.new.foo(100)).to eq 100
|
145
|
+
end
|
143
146
|
end
|
144
|
-
end
|
145
147
|
|
146
|
-
|
148
|
+
context 'function with no parameters' do
|
147
149
|
|
148
|
-
|
150
|
+
it 'accepts no parameters' do
|
149
151
|
|
150
|
-
|
151
|
-
|
152
|
+
subject.defn(:foo){}
|
153
|
+
obj = subject.new
|
152
154
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
155
|
+
expect {
|
156
|
+
obj.foo
|
157
|
+
}.not_to raise_error
|
158
|
+
end
|
157
159
|
|
158
|
-
|
160
|
+
it 'does not accept any parameters' do
|
159
161
|
|
160
|
-
|
161
|
-
|
162
|
+
subject.defn(:foo){}
|
163
|
+
obj = subject.new
|
162
164
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
165
|
+
expect {
|
166
|
+
obj.foo(1)
|
167
|
+
}.to raise_error(NoMethodError)
|
168
|
+
end
|
167
169
|
|
168
|
-
|
169
|
-
|
170
|
-
|
170
|
+
it 'returns the correct value' do
|
171
|
+
subject.defn(:foo){ true }
|
172
|
+
expect(subject.new.foo).to be true
|
173
|
+
end
|
171
174
|
end
|
172
|
-
end
|
173
175
|
|
174
|
-
|
176
|
+
context 'function with one parameter' do
|
175
177
|
|
176
|
-
|
178
|
+
it 'matches a nil parameter' do
|
177
179
|
|
178
|
-
|
179
|
-
|
180
|
+
subject.defn(:foo, nil) { 'expected' }
|
181
|
+
expect(subject.new.foo(nil)).to eq 'expected'
|
180
182
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
183
|
+
expect {
|
184
|
+
subject.new.foo('no match should be found')
|
185
|
+
}.to raise_error(NoMethodError)
|
186
|
+
end
|
185
187
|
|
186
|
-
|
188
|
+
it 'matches a boolean parameter' do
|
187
189
|
|
188
|
-
|
189
|
-
|
190
|
+
subject.defn(:foo, true) { 'expected' }
|
191
|
+
subject.defn(:foo, false) { 'false case' }
|
190
192
|
|
191
|
-
|
192
|
-
|
193
|
+
expect(subject.new.foo(true)).to eq 'expected'
|
194
|
+
expect(subject.new.foo(false)).to eq 'false case'
|
193
195
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
196
|
+
expect {
|
197
|
+
subject.new.foo('no match should be found')
|
198
|
+
}.to raise_error(NoMethodError)
|
199
|
+
end
|
198
200
|
|
199
|
-
|
201
|
+
it 'matches a symbol parameter' do
|
200
202
|
|
201
|
-
|
202
|
-
|
203
|
+
subject.defn(:foo, :bar) { 'expected' }
|
204
|
+
expect(subject.new.foo(:bar)).to eq 'expected'
|
203
205
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
206
|
+
expect {
|
207
|
+
subject.new.foo(:baz)
|
208
|
+
}.to raise_error(NoMethodError)
|
209
|
+
end
|
208
210
|
|
209
|
-
|
211
|
+
it 'matches a number parameter' do
|
210
212
|
|
211
|
-
|
212
|
-
|
213
|
+
subject.defn(:foo, 10) { 'expected' }
|
214
|
+
expect(subject.new.foo(10)).to eq 'expected'
|
213
215
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
216
|
+
expect {
|
217
|
+
subject.new.foo(11.0)
|
218
|
+
}.to raise_error(NoMethodError)
|
219
|
+
end
|
218
220
|
|
219
|
-
|
221
|
+
it 'matches a string parameter' do
|
220
222
|
|
221
|
-
|
222
|
-
|
223
|
+
subject.defn(:foo, 'bar') { 'expected' }
|
224
|
+
expect(subject.new.foo('bar')).to eq 'expected'
|
223
225
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
226
|
+
expect {
|
227
|
+
subject.new.foo('baz')
|
228
|
+
}.to raise_error(NoMethodError)
|
229
|
+
end
|
228
230
|
|
229
|
-
|
231
|
+
it 'matches an array parameter' do
|
230
232
|
|
231
|
-
|
232
|
-
|
233
|
+
subject.defn(:foo, [1, 2, 3]) { 'expected' }
|
234
|
+
expect(subject.new.foo([1, 2, 3])).to eq 'expected'
|
233
235
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
236
|
+
expect {
|
237
|
+
subject.new.foo([3, 4, 5])
|
238
|
+
}.to raise_error(NoMethodError)
|
239
|
+
end
|
238
240
|
|
239
|
-
|
241
|
+
it 'matches a hash parameter' do
|
240
242
|
|
241
|
-
|
242
|
-
|
243
|
+
subject.defn(:foo, bar: 1, baz: 2) { 'expected' }
|
244
|
+
expect(subject.new.foo(bar: 1, baz: 2)).to eq 'expected'
|
243
245
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
246
|
+
expect {
|
247
|
+
subject.new.foo(foo: 0, bar: 1)
|
248
|
+
}.to raise_error(NoMethodError)
|
249
|
+
end
|
248
250
|
|
249
|
-
|
251
|
+
it 'matches an object parameter' do
|
250
252
|
|
251
|
-
|
252
|
-
|
253
|
+
subject.defn(:foo, OpenStruct.new(foo: :bar)) { 'expected' }
|
254
|
+
expect(subject.new.foo(OpenStruct.new(foo: :bar))).to eq 'expected'
|
253
255
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
256
|
+
expect {
|
257
|
+
subject.new.foo(OpenStruct.new(bar: :baz))
|
258
|
+
}.to raise_error(NoMethodError)
|
259
|
+
end
|
258
260
|
|
259
|
-
|
261
|
+
it 'matches an unbound parameter' do
|
260
262
|
|
261
|
-
|
262
|
-
|
263
|
+
subject.defn(:foo, PatternMatching::UNBOUND) {|arg| arg }
|
264
|
+
expect(subject.new.foo(:foo)).to eq :foo
|
265
|
+
end
|
263
266
|
end
|
264
|
-
end
|
265
267
|
|
266
|
-
|
268
|
+
context 'function with two parameters' do
|
267
269
|
|
268
|
-
|
270
|
+
it 'matches two bound arguments' do
|
269
271
|
|
270
|
-
|
271
|
-
|
272
|
+
subject.defn(:foo, :male, :female){ 'expected' }
|
273
|
+
expect(subject.new.foo(:male, :female)).to eq 'expected'
|
272
274
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
275
|
+
expect {
|
276
|
+
subject.new.foo(1, 2)
|
277
|
+
}.to raise_error(NoMethodError)
|
278
|
+
end
|
277
279
|
|
278
|
-
|
280
|
+
it 'matches two unbound arguments' do
|
279
281
|
|
280
|
-
|
281
|
-
|
282
|
+
subject.defn(:foo, PatternMatching::UNBOUND, PatternMatching::UNBOUND) do |first, second|
|
283
|
+
[first, second]
|
284
|
+
end
|
285
|
+
expect(subject.new.foo(:male, :female)).to eq [:male, :female]
|
282
286
|
end
|
283
|
-
subject.new.foo(:male, :female).should eq [:male, :female]
|
284
|
-
end
|
285
287
|
|
286
|
-
|
288
|
+
it 'matches when the first argument is bound and the second is not' do
|
287
289
|
|
288
|
-
|
289
|
-
|
290
|
+
subject.defn(:foo, :male, PatternMatching::UNBOUND) do |second|
|
291
|
+
second
|
292
|
+
end
|
293
|
+
expect(subject.new.foo(:male, :female)).to eq :female
|
290
294
|
end
|
291
|
-
subject.new.foo(:male, :female).should eq :female
|
292
|
-
end
|
293
295
|
|
294
|
-
|
296
|
+
it 'matches when the second argument is bound and the first is not' do
|
295
297
|
|
296
|
-
|
297
|
-
|
298
|
+
subject.defn(:foo, PatternMatching::UNBOUND, :female) do |first|
|
299
|
+
first
|
300
|
+
end
|
301
|
+
expect(subject.new.foo(:male, :female)).to eq :male
|
298
302
|
end
|
299
|
-
subject.new.foo(:male, :female).should eq :male
|
300
303
|
end
|
301
|
-
end
|
302
304
|
|
303
|
-
|
305
|
+
context 'functions with hash arguments' do
|
304
306
|
|
305
|
-
|
307
|
+
it 'matches an empty argument hash with an empty parameter hash' do
|
306
308
|
|
307
|
-
|
308
|
-
|
309
|
+
subject.defn(:foo, {}) { true }
|
310
|
+
expect(subject.new.foo({})).to be true
|
309
311
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
312
|
+
expect {
|
313
|
+
subject.new.foo({one: :two})
|
314
|
+
}.to raise_error(NoMethodError)
|
315
|
+
end
|
314
316
|
|
315
|
-
|
317
|
+
it 'matches when all hash keys and values match' do
|
316
318
|
|
317
|
-
|
318
|
-
|
319
|
+
subject.defn(:foo, {bar: :baz}) { true }
|
320
|
+
expect(subject.new.foo(bar: :baz)).to be true
|
319
321
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
322
|
+
expect {
|
323
|
+
subject.new.foo({one: :two})
|
324
|
+
}.to raise_error(NoMethodError)
|
325
|
+
end
|
324
326
|
|
325
|
-
|
327
|
+
it 'matches when every pattern key/value are in the argument' do
|
326
328
|
|
327
|
-
|
328
|
-
|
329
|
-
|
329
|
+
subject.defn(:foo, {bar: :baz}) { true }
|
330
|
+
expect(subject.new.foo(foo: :bar, bar: :baz)).to be true
|
331
|
+
end
|
330
332
|
|
331
|
-
|
333
|
+
it 'matches when all keys with unbound values in the pattern have an argument' do
|
332
334
|
|
333
|
-
|
334
|
-
|
335
|
-
|
335
|
+
subject.defn(:foo, {bar: PatternMatching::UNBOUND}) { true }
|
336
|
+
expect(subject.new.foo(bar: :baz)).to be true
|
337
|
+
end
|
336
338
|
|
337
|
-
|
339
|
+
it 'passes unbound values to the block' do
|
338
340
|
|
339
|
-
|
340
|
-
|
341
|
-
|
341
|
+
subject.defn(:foo, {bar: PatternMatching::UNBOUND}) {|arg| arg }
|
342
|
+
expect(subject.new.foo(bar: :baz)).to eq :baz
|
343
|
+
end
|
342
344
|
|
343
|
-
|
345
|
+
it 'passes the matched hash to the block' do
|
344
346
|
|
345
|
-
|
346
|
-
|
347
|
-
|
347
|
+
subject.defn(:foo, {bar: :baz}) { |opts| opts }
|
348
|
+
expect(subject.new.foo(bar: :baz)).to eq({bar: :baz})
|
349
|
+
end
|
348
350
|
|
349
|
-
|
351
|
+
it 'does not match a non-hash argument' do
|
350
352
|
|
351
|
-
|
353
|
+
subject.defn(:foo, {}) { true }
|
352
354
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
355
|
+
expect {
|
356
|
+
subject.new.foo(:bar)
|
357
|
+
}.to raise_error(NoMethodError)
|
358
|
+
end
|
357
359
|
|
358
|
-
|
360
|
+
it 'supports idiomatic has-as-last-argument syntax' do
|
359
361
|
|
360
|
-
|
361
|
-
|
362
|
+
subject.defn(:foo, PatternMatching::UNBOUND) { |opts| opts }
|
363
|
+
expect(subject.new.foo(bar: :baz, one: 1, many: 2)).to eq({bar: :baz, one: 1, many: 2})
|
364
|
+
end
|
362
365
|
end
|
363
|
-
end
|
364
366
|
|
365
|
-
|
367
|
+
context 'varaible-length argument lists' do
|
368
|
+
|
369
|
+
it 'supports ALL as the last parameter' do
|
366
370
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
subject.new.foo(1, 2, :foo, :bar, one: 1, two: 2).should == [:foo, :bar, {one: 1, two: 2}]
|
371
|
+
subject.defn(:foo, 1, 2, PatternMatching::ALL) { |args| args }
|
372
|
+
expect(subject.new.foo(1, 2, 3)).to eq([3])
|
373
|
+
expect(subject.new.foo(1, 2, :foo, :bar)).to eq([:foo, :bar])
|
374
|
+
expect(subject.new.foo(1, 2, :foo, :bar, one: 1, two: 2)).to eq([:foo, :bar, {one: 1, two: 2}])
|
375
|
+
end
|
373
376
|
end
|
374
|
-
end
|
375
377
|
|
376
|
-
|
378
|
+
context 'guard clauses' do
|
377
379
|
|
378
|
-
|
380
|
+
it 'matches when the guard clause returns true' do
|
379
381
|
|
380
|
-
|
381
|
-
|
382
|
-
|
382
|
+
subject.defn(:old_enough, PatternMatching::UNBOUND){
|
383
|
+
true
|
384
|
+
}.when{|x| x > 16 }
|
383
385
|
|
384
|
-
|
385
|
-
|
386
|
+
expect(subject.new.old_enough(20)).to be true
|
387
|
+
end
|
386
388
|
|
387
|
-
|
389
|
+
it 'does not match when the guard clause returns false' do
|
388
390
|
|
389
|
-
|
390
|
-
|
391
|
-
|
391
|
+
subject.defn(:old_enough, PatternMatching::UNBOUND){
|
392
|
+
true
|
393
|
+
}.when{|x| x > 16 }
|
392
394
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
395
|
+
expect {
|
396
|
+
subject.new.old_enough(10)
|
397
|
+
}.to raise_error(NoMethodError)
|
398
|
+
end
|
397
399
|
|
398
|
-
|
400
|
+
it 'continues pattern matching when the guard clause returns false' do
|
399
401
|
|
400
|
-
|
401
|
-
|
402
|
-
|
402
|
+
subject.defn(:old_enough, PatternMatching::UNBOUND){
|
403
|
+
true
|
404
|
+
}.when{|x| x > 16 }
|
403
405
|
|
404
|
-
|
406
|
+
subject.defn(:old_enough, PatternMatching::UNBOUND) { false }
|
405
407
|
|
406
|
-
|
407
|
-
|
408
|
+
expect(subject.new.old_enough(10)).to be false
|
409
|
+
end
|
408
410
|
|
409
|
-
|
411
|
+
it 'raises an exception when the guard clause does not have a block' do
|
410
412
|
|
411
|
-
|
412
|
-
|
413
|
-
|
413
|
+
expect {
|
414
|
+
subject.defn(:initialize, PatternMatching::UNBOUND) { 'one arg' }.when
|
415
|
+
}.to raise_error(ArgumentError)
|
416
|
+
end
|
414
417
|
end
|
415
418
|
end
|
416
419
|
end
|