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