functional-ruby 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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