pattern-matching 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 26d3f6768306dd2dba389a7dd481e135417164de
4
- data.tar.gz: 5bdc90e25e848b7de5ce67d58f7cce70d1a7e434
5
- SHA512:
6
- metadata.gz: e6fd87d44de49a937de38e70e42c7ab470b0afce1f20fb5ef64ecb949234ce168b0ba75713394457c75c7de02565a86ee47eee26ea2f6b04b7a1e8da8b7368b2
7
- data.tar.gz: fb843fe146735c3603eb641b0e2dc790da3be503889a13e8b737844598b2012fceb2f48974dda9aa955c0147348fa1dea784edfb4d2e647eb0b619d2636dd4a4
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDIzMzQ1NTgyM2ZlNTVkMmEzYmI1YTNhZWMzODJjYWY5ODkxNzI5NQ==
5
+ data.tar.gz: !binary |-
6
+ MTcyYWU2ZjE1MGYxMWQxYWRkMWQwZGEwMDZmNDNiZWRkM2JjNjk1YQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZDdmMjE5NjFhOTI1MDE4MzFkZjRjMDgxZWY2OGMzNzNlODczMjQ4ZjRiZTE2
10
+ MjZkZDNhYjFhOTBmODJiMTFlYzQ5ZjkwNzQzOTRjZmM5YjQzZjI3MTg3MTNi
11
+ ZGI5MmI4ZWNmMTYyMTJkNDA3NTc3ZjJlNGEzMzM4NWE0Mzk4MTE=
12
+ data.tar.gz: !binary |-
13
+ MzJkN2M4M2YzYTQyODkzODI5YzlkNjZmMWFjZDAzY2VlNTg0OGVjOWUzYmRh
14
+ NzJiNjFkOWI0ODY5NjE4ODcyZTBiYjljNGYyYWQ5ZGE5ZmNjOThmZTY5YTRh
15
+ M2M5NThkNmQ3NGNkMDJjYWEwYTNhZWYwZjJkYmNlYzQ5YmIwNzc=
data/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- Copyright (c) Jerry D'Antonio -- released under the MIT license.
2
-
3
- http://www.opensource.org/licenses/mit-license.php
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ Copyright (c) Jerry D'Antonio -- released under the MIT license.
2
+
3
+ http://www.opensource.org/licenses/mit-license.php
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,476 +1,536 @@
1
- # PatternMatching [![Build Status](https://secure.travis-ci.org/jdantonio/pattern_matching.png)](http://travis-ci.org/jdantonio/pattern_matching?branch=master) [![Dependency Status](https://gemnasium.com/jdantonio/pattern_matching.png)](https://gemnasium.com/jdantonio/pattern_matching)
2
-
3
- A gem for adding Erlang-style pattern matching to Ruby classes.
4
-
5
- *NOTE: This is a work in progress. Expect changes.*
6
-
7
- The project is hosted on the following sites:
8
-
9
- * [RubyGems project page](https://rubygems.org/gems/pattern-matching)
10
- * [Source code on GitHub](https://github.com/jdantonio/pattern_matching)
11
- * [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/pattern_matching)
12
- * [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/pattern_matching)
13
- * [Follow me on Twitter](https://twitter.com/jerrydantonio)
14
-
15
- ## Introduction
16
-
17
- [Ruby](http://www.ruby-lang.org/en/) is my favorite programming by far. As much as I love Ruby I've always been a little disappointed that Ruby doesn't support function overloading. Function overloading tends to reduce branching and keep functions signatures simpler. No sweat, I learned to do without. Then I started programming in [Erlang](http://www.erlang.org/)…
18
-
19
- I've really started to enjoy working in Erlang. Erlang is good at all the things Ruby is bad at and vice versa. Together, Ruby and Erlang make me happy. My favotite Erlang feature is, without question, [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching). Pattern matching is like function overloading cranked to 11. So one day I was musing on Twitter and one of my friends responded with "Build it!" So I did. And here it is.
20
-
21
- ### Goals
22
-
23
- * Stay true to the spirit of Erlang pattern matching, if not the semantics
24
- * Keep the semantics as idiomatic Ruby as possible
25
- * Support features that make sense in Ruby
26
- * Exclude features that only make sense in Erlang
27
- * Avoid using *method_missing*
28
- * Keep it small (currently at 72 LOC)
29
-
30
- ### Features
31
-
32
- * Basic pattern matching for instance methods.
33
- * Parameter count matching
34
- * Mathing against primitive values
35
- * Matching by class/datatype
36
- * Matching against specific key/vaue pairs in hashes
37
- * Matching against the presence of keys within hashes
38
- * Reasonable error messages when no match is found
39
- * Dispatching to superclass methods when no match is found
40
- * Recursive calls to other pattern matches
41
- * Recursive calls to superclass methods
42
-
43
- ### To-do
44
-
45
- * Matching against array elements
46
- * Guard clauses
47
- * Support class methods
48
- * Support module instance methods
49
- * Support module methods
50
-
51
- ## Supported Ruby versions
52
-
53
- MRI 1.9.x and above. Anything else and your mileage may vary.
54
-
55
- ## Install
56
-
57
- ```shell
58
- gem install pattern-matching
59
- ```
60
-
61
- or add the following line to Gemfile:
62
-
63
- ```ruby
64
- gem 'pattern-matching'
65
- ```
66
-
67
- and run `bundle install` from your shell.
68
-
69
- ## Usage
70
-
71
- First, familiarize yourself with Erlang [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching).
72
- This gem may not make much sense if you don't understand how Erlang dispatches
73
- functions.
74
-
75
- In the Ruby class file where you want to use pattern matching, require the
76
- *pattern_matching* gem:
77
-
78
- ```ruby
79
- require 'pattern_matching'
80
- ```
81
-
82
- Then include `PatternMatching` in your class:
83
-
84
- ```ruby
85
- require 'pattern_matching'
86
-
87
- class Foo
88
- include PatternMatching
89
-
90
- ...
91
-
92
- end
93
- ```
94
-
95
- You can then define functions with `defn` instead of the normal *def* statement.
96
- The syntax for `defn` is:
97
-
98
- ```ruby
99
- defn(:symbol_name_of_function, zero, or, more, parameters) { |block, arguments|
100
- # code to execute
101
- }
102
- ```
103
- You can then call your new function just like any other:
104
-
105
- ```ruby
106
- require 'pattern_matching'
107
-
108
- class Foo
109
- include PatternMatching
110
-
111
- defn(:hello) {
112
- puts "Hello, World!"
113
- }
114
- end
115
-
116
- foo = Foo.new
117
- foo.hello #=> "Hello, World!"
118
- ```
119
-
120
- Patterns to match against are included in the parameter list:
121
-
122
- ```ruby
123
- defn(:greet, :male) {
124
- puts "Hello, sir!"
125
- }
126
-
127
- defn(:greet, :female) {
128
- puts "Hello, ma'am!"
129
- }
130
-
131
- ...
132
-
133
- foo.hello(:male) #=> "Hello, sir!"
134
- foo.hello(:female) #=> "Hello, ma'am!"
135
- ```
136
-
137
- If a particular method call can not be matched a *NoMethodError* is thrown with
138
- a reasonably helpful error message:
139
-
140
- ```ruby
141
- foo.greet(:unknown) #=> NoMethodError: no method `greet` matching [:unknown] found for class Foo
142
- foo.greet #=> NoMethodError: no method `greet` matching [] found for class Foo
143
- ```
144
-
145
- Parameters that are expected to exist but that can take any value are considered
146
- *unbound* parameters. Unbound parameters are specified by the `_` underscore
147
- character or `UNBOUND`:
148
-
149
- ```ruby
150
- defn(:greet, _) do |name|
151
- "Hello, #{name}!"
152
- end
153
-
154
- defn(:greet, UNBOUND, UNBOUND) do |first, last|
155
- "Hello, #{first} #{last}!"
156
- end
157
-
158
- ...
159
-
160
- foo.greet('Jerry') #=> "Hello, Jerry!"
161
- ```
162
-
163
- All unbound parameters will be passed to the block in the order they are specified in the definition:
164
-
165
- ```ruby
166
- defn(:greet, _, _) do |first, last|
167
- "Hello, #{first} #{last}!"
168
- end
169
-
170
- ...
171
-
172
- foo.greet('Jerry', "D'Antonio") #=> "Hello, Jerry D'Antonio!"
173
- ```
174
-
175
- If for some reason you don't care about one or more unbound parameters within
176
- the block you can use the `_` underscore character in the block parameters list
177
- as well:
178
-
179
- ```ruby
180
- defn(:greet, _, _, _) do |first, _, last|
181
- "Hello, #{first} #{last}!"
182
- end
183
-
184
- ...
185
-
186
- foo.greet('Jerry', "I'm not going to tell you my middle name!", "D'Antonio") #=> "Hello, Jerry D'Antonio!"
187
- ```
188
-
189
- Hash parameters can match against specific keys and either bound or unbound parameters. This allows for function dispatch by hash parameters without having to dig through the hash:
190
-
191
- ```ruby
192
- defn(:hashable, {foo: :bar}) { |opts|
193
- :foo_bar
194
- }
195
- defn(:hashable, {foo: _}) { |f|
196
- f
197
- }
198
-
199
- ...
200
-
201
- foo.hashable({foo: :bar}) #=> :foo_bar
202
- foo.hashable({foo: :baz}) #=> :baz
203
- ```
204
-
205
- The Ruby idiom of the final parameter being a hash is also supported:
206
-
207
- ```ruby
208
- defn(:options, _) { |opts|
209
- opts
210
- }
211
-
212
- ...
213
-
214
- foo.options(bar: :baz, one: 1, many: 2)
215
- ```
216
-
217
- As is the Ruby idiom of variable-length argument lists. The constant `ALL` as the last parameter
218
- will match one or more arguments and pass them to the block as an array:
219
-
220
- ```ruby
221
- defn(:baz, Integer, ALL) { |int, args|
222
- [int, args]
223
- }
224
- defn(:baz, ALL) { |args|
225
- args
226
- }
227
- ```
228
-
229
- Superclass polymorphism is supported as well. If an object cannot match a method
230
- signature it will defer to the parent class:
231
-
232
- ```ruby
233
- class Bar
234
- def greet
235
- return 'Hello, World!'
236
- end
237
- end
238
-
239
- class Foo < Bar
240
- include PatternMatching
241
-
242
- defn(:greet, _) do |name|
243
- "Hello, #{name}!"
244
- end
245
- end
246
-
247
- ...
248
-
249
- foo.greet('Jerry') #=> "Hello, Jerry!"
250
- foo.greet #=> "Hello, World!"
251
- ```
252
-
253
- ### Order Matters
254
-
255
- As with Erlang, the order of pattern matches is significant. Patterns will be matched
256
- *in the order declared* and the first match will be used. If a particular function call
257
- can be matched by more than one pattern, the *first matched pattern* will be used. It
258
- is the programmer's responsibility to ensure patterns are declared in the correct order.
259
-
260
- ### Blocks and Procs and Lambdas, oh my!
261
-
262
- When using this gem it is critical to remember that `defn` takes a block and
263
- that blocks in Ruby have special rules. There are [plenty](https://www.google.com/search?q=ruby+block+proc+lambda)
264
- of good tutorials on the web explaining [blocks](http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/)
265
- and [Procs](https://coderwall.com/p/_-_mha) and [lambdas](http://railsguru.org/2010/03/learn-ruby-procs-blocks-lambda/)
266
- in Ruby. Please read them. Please don't submit a bug report if you use a
267
- `return` statement within your `defn` and your code blows up with a
268
- [LocalJumpError](http://ruby-doc.org/core-2.0/LocalJumpError.html).
269
-
270
- ## Examples
271
-
272
- For more examples see the integration tests in *spec/integration_spec.rb*.
273
-
274
- ### Simple Functions
275
-
276
- This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
277
-
278
- Erlang:
279
-
280
- ```erlang
281
- greet(male, Name) ->
282
- io:format("Hello, Mr. ~s!", [Name]);
283
- greet(female, Name) ->
284
- io:format("Hello, Mrs. ~s!", [Name]);
285
- greet(_, Name) ->
286
- io:format("Hello, ~s!", [Name]).
287
- ```
288
-
289
- Ruby:
290
-
291
- ```ruby
292
- require 'pattern_matching'
293
-
294
- class Foo
295
- include PatternMatching
296
-
297
- defn(:greet, _) do |name|
298
- "Hello, #{name}!"
299
- end
300
-
301
- defn(:greet, :male, _) { |name|
302
- "Hello, Mr. #{name}!"
303
- }
304
- defn(:greet, :female, _) { |name|
305
- "Hello, Ms. #{name}!"
306
- }
307
- defn(:greet, _, _) { |_, name|
308
- "Hello, #{name}!"
309
- }
310
- end
311
- ```
312
-
313
- ### Simple Functions with Overloading
314
-
315
- This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
316
-
317
- Erlang:
318
-
319
- ```erlang
320
- greet(Name) ->
321
- io:format("Hello, ~s!", [Name]).
322
-
323
- greet(male, Name) ->
324
- io:format("Hello, Mr. ~s!", [Name]);
325
- greet(female, Name) ->
326
- io:format("Hello, Mrs. ~s!", [Name]);
327
- greet(_, Name) ->
328
- io:format("Hello, ~s!", [Name]).
329
- ```
330
-
331
- Ruby:
332
-
333
- ```ruby
334
- require 'pattern_matching'
335
-
336
- class Foo
337
- include PatternMatching
338
-
339
- defn(:greet, _) do |name|
340
- "Hello, #{name}!"
341
- end
342
-
343
- defn(:greet, :male, _) { |name|
344
- "Hello, Mr. #{name}!"
345
- }
346
- defn(:greet, :female, _) { |name|
347
- "Hello, Ms. #{name}!"
348
- }
349
- defn(:greet, nil, _) { |name|
350
- "Goodbye, #{name}!"
351
- }
352
- defn(:greet, _, _) { |_, name|
353
- "Hello, #{name}!"
354
- }
355
- end
356
- ```
357
-
358
- ### Matching by Class/Datatype
359
-
360
- ```ruby
361
- require 'pattern_matching'
362
-
363
- class Foo
364
- include PatternMatching
365
-
366
- defn(:concat, Integer, Integer) { |first, second|
367
- first + second
368
- }
369
- defn(:concat, Integer, String) { |first, second|
370
- "#{first} #{second}"
371
- }
372
- defn(:concat, String, String) { |first, second|
373
- first + second
374
- }
375
- defn(:concat, Integer, _) { |first, second|
376
- first + second.to_i
377
- }
378
- end
379
- ```
380
-
381
- ### Matching a Hash Parameter
382
-
383
- ```ruby
384
- require 'pattern_matching'
385
-
386
- class Foo
387
- include PatternMatching
388
-
389
- defn(:hashable, {foo: :bar}) { |opts|
390
- # matches any hash with key :foo and value :bar
391
- :foo_bar
392
- }
393
- defn(:hashable, {foo: _, bar: _}) { |f, b|
394
- # matches any hash with keys :foo and :bar
395
- # passes the values associated with those keys to the block
396
- [f, b]
397
- }
398
- defn(:hashable, {foo: _}) { |f|
399
- # matches any hash with key :foo
400
- # passes the value associated with that key to the block
401
- # must appear AFTER the prior match or it will override that one
402
- f
403
- }
404
- defn(:hashable, {}) { ||
405
- # matches an empty hash
406
- :empty
407
- }
408
- defn(:hashable, _) { |opts|
409
- # matches any hash (or any other value)
410
- opts
411
- }
412
- end
413
-
414
- ...
415
-
416
- foo.hashable({foo: :bar}) #=> :foo_bar
417
- foo.hashable({foo: :baz}) #=> :baz
418
- foo.hashable({foo: 1, bar: 2}) #=> [1, 2]
419
- foo.hashable({foo: 1, baz: 2}) #=> 1
420
- foo.hashable({bar: :baz}) #=> {bar: :baz}
421
- foo.hashable({}) #=> :empty
422
- ```
423
-
424
- ### Variable Length Argument Lists with ALL
425
-
426
- ```ruby
427
- defn(:all, :one, ALL) { |args|
428
- args
429
- }
430
- defn(:all, :one, Integer, ALL) { |int, args|
431
- [int, args]
432
- }
433
- defn(:all, 1, _, ALL) { |var, args|
434
- [var, args]
435
- }
436
- defn(:all, ALL) { | args|
437
- args
438
- }
439
-
440
- ...
441
-
442
- foo.all(:one, 'a', 'bee', :see) #=> ['a', 'bee', :see]
443
- foo.all(:one, 1, 'bee', :see) #=> [1, 'bee', :see]
444
- foo.all(1, 'a', 'bee', :see) #=> ['a', ['bee', :see]]
445
- foo.all('a', 'bee', :see) #=> ['a', 'bee', :see]
446
- foo.all() #=> NoMethodError: no method `all` matching [] found for class Foo
447
- ```
448
-
449
- ## Copyright
450
-
451
- *PatternMatching* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
452
- It is free software and may be redistributed under the terms specified in the LICENSE file.
453
-
454
- ## License
455
-
456
- Released under the MIT license.
457
-
458
- http://www.opensource.org/licenses/mit-license.php
459
-
460
- > Permission is hereby granted, free of charge, to any person obtaining a copy
461
- > of this software and associated documentation files (the "Software"), to deal
462
- > in the Software without restriction, including without limitation the rights
463
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
464
- > copies of the Software, and to permit persons to whom the Software is
465
- > furnished to do so, subject to the following conditions:
466
- >
467
- > The above copyright notice and this permission notice shall be included in
468
- > all copies or substantial portions of the Software.
469
- >
470
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
471
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
472
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
473
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
474
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
475
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
476
- > THE SOFTWARE.
1
+ # PatternMatching [![Build Status](https://secure.travis-ci.org/jdantonio/pattern_matching.png)](http://travis-ci.org/jdantonio/pattern_matching?branch=master) [![Dependency Status](https://gemnasium.com/jdantonio/pattern_matching.png)](https://gemnasium.com/jdantonio/pattern_matching)
2
+
3
+ A gem for adding Erlang-style function/method overloading through pattern matching to Ruby classes.
4
+
5
+ *NOTE: This is a work in progress. Expect changes.*
6
+
7
+ The project is hosted on the following sites:
8
+
9
+ * [RubyGems project page](https://rubygems.org/gems/pattern-matching)
10
+ * [Source code on GitHub](https://github.com/jdantonio/pattern_matching)
11
+ * [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/pattern_matching)
12
+ * [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/pattern_matching)
13
+ * [Follow me on Twitter](https://twitter.com/jerrydantonio)
14
+
15
+ ## Introduction
16
+
17
+ [Ruby](http://www.ruby-lang.org/en/) is my favorite programming by far. As much as I love Ruby I've always been a little disappointed that Ruby doesn't support function overloading. Function overloading tends to reduce branching and keep functions signatures simpler. No sweat, I learned to do without. Then I started programming in [Erlang](http://www.erlang.org/)…
18
+
19
+ I've really started to enjoy working in Erlang. Erlang is good at all the things Ruby is bad at and vice versa. Together, Ruby and Erlang make me happy. My favotite Erlang feature is, without question, [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching). Pattern matching is like function overloading cranked to 11. So one day I was musing on Twitter and one of my friends responded with "Build it!" So I did. And here it is.
20
+
21
+ ### Goals
22
+
23
+ * Stay true to the spirit of Erlang pattern matching, if not the semantics
24
+ * Keep the semantics as idiomatic Ruby as possible
25
+ * Support features that make sense in Ruby
26
+ * Exclude features that only make sense in Erlang
27
+ * Avoid using *method_missing*
28
+ * Keep it small (currently arround 100 LOC)
29
+ * Be as fast as reasonably possible
30
+
31
+ ### Features
32
+
33
+ * Basic pattern matching for instance methods.
34
+ * Parameter count matching
35
+ * Mathing against primitive values
36
+ * Matching by class/datatype
37
+ * Matching against specific key/vaue pairs in hashes
38
+ * Matching against the presence of keys within hashes
39
+ * Reasonable error messages when no match is found
40
+ * Dispatching to superclass methods when no match is found
41
+ * Recursive calls to other pattern matches
42
+ * Recursive calls to superclass methods
43
+
44
+ ### To-do
45
+
46
+ * Support class methods
47
+ * Support module instance methods
48
+ * Support module methods
49
+
50
+ ## Supported Ruby versions
51
+
52
+ MRI 1.9.x and above. Anything else and your mileage may vary.
53
+
54
+ ## Install
55
+
56
+ ```shell
57
+ gem install pattern-matching
58
+ ```
59
+
60
+ or add the following line to Gemfile:
61
+
62
+ ```ruby
63
+ gem 'pattern-matching'
64
+ ```
65
+
66
+ and run `bundle install` from your shell.
67
+
68
+ ## Usage
69
+
70
+ First, familiarize yourself with Erlang [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching).
71
+ This gem may not make much sense if you don't understand how Erlang dispatches
72
+ functions.
73
+
74
+ In the Ruby class file where you want to use pattern matching, require the
75
+ *pattern_matching* gem:
76
+
77
+ ```ruby
78
+ require 'pattern_matching'
79
+ ```
80
+
81
+ Then include `PatternMatching` in your class:
82
+
83
+ ```ruby
84
+ require 'pattern_matching'
85
+
86
+ class Foo
87
+ include PatternMatching
88
+
89
+ ...
90
+
91
+ end
92
+ ```
93
+
94
+ You can then define functions with `defn` instead of the normal *def* statement.
95
+ The syntax for `defn` is:
96
+
97
+ ```ruby
98
+ defn(:symbol_name_of_function, zero, or, more, parameters) { |block, arguments|
99
+ # code to execute
100
+ }
101
+ ```
102
+ You can then call your new function just like any other:
103
+
104
+ ```ruby
105
+ require 'pattern_matching'
106
+
107
+ class Foo
108
+ include PatternMatching
109
+
110
+ defn(:hello) {
111
+ puts "Hello, World!"
112
+ }
113
+ end
114
+
115
+ foo = Foo.new
116
+ foo.hello #=> "Hello, World!"
117
+ ```
118
+
119
+ Patterns to match against are included in the parameter list:
120
+
121
+ ```ruby
122
+ defn(:greet, :male) {
123
+ puts "Hello, sir!"
124
+ }
125
+
126
+ defn(:greet, :female) {
127
+ puts "Hello, ma'am!"
128
+ }
129
+
130
+ ...
131
+
132
+ foo.hello(:male) #=> "Hello, sir!"
133
+ foo.hello(:female) #=> "Hello, ma'am!"
134
+ ```
135
+
136
+ If a particular method call can not be matched a *NoMethodError* is thrown with
137
+ a reasonably helpful error message:
138
+
139
+ ```ruby
140
+ foo.greet(:unknown) #=> NoMethodError: no method `greet` matching [:unknown] found for class Foo
141
+ foo.greet #=> NoMethodError: no method `greet` matching [] found for class Foo
142
+ ```
143
+
144
+ Parameters that are expected to exist but that can take any value are considered
145
+ *unbound* parameters. Unbound parameters are specified by the `_` underscore
146
+ character or `UNBOUND`:
147
+
148
+ ```ruby
149
+ defn(:greet, _) do |name|
150
+ "Hello, #{name}!"
151
+ end
152
+
153
+ defn(:greet, UNBOUND, UNBOUND) do |first, last|
154
+ "Hello, #{first} #{last}!"
155
+ end
156
+
157
+ ...
158
+
159
+ foo.greet('Jerry') #=> "Hello, Jerry!"
160
+ ```
161
+
162
+ All unbound parameters will be passed to the block in the order they are specified in the definition:
163
+
164
+ ```ruby
165
+ defn(:greet, _, _) do |first, last|
166
+ "Hello, #{first} #{last}!"
167
+ end
168
+
169
+ ...
170
+
171
+ foo.greet('Jerry', "D'Antonio") #=> "Hello, Jerry D'Antonio!"
172
+ ```
173
+
174
+ If for some reason you don't care about one or more unbound parameters within
175
+ the block you can use the `_` underscore character in the block parameters list
176
+ as well:
177
+
178
+ ```ruby
179
+ defn(:greet, _, _, _) do |first, _, last|
180
+ "Hello, #{first} #{last}!"
181
+ end
182
+
183
+ ...
184
+
185
+ foo.greet('Jerry', "I'm not going to tell you my middle name!", "D'Antonio") #=> "Hello, Jerry D'Antonio!"
186
+ ```
187
+
188
+ Hash parameters can match against specific keys and either bound or unbound parameters. This allows for function dispatch by hash parameters without having to dig through the hash:
189
+
190
+ ```ruby
191
+ defn(:hashable, {foo: :bar}) { |opts|
192
+ :foo_bar
193
+ }
194
+ defn(:hashable, {foo: _}) { |f|
195
+ f
196
+ }
197
+
198
+ ...
199
+
200
+ foo.hashable({foo: :bar}) #=> :foo_bar
201
+ foo.hashable({foo: :baz}) #=> :baz
202
+ ```
203
+
204
+ The Ruby idiom of the final parameter being a hash is also supported:
205
+
206
+ ```ruby
207
+ defn(:options, _) { |opts|
208
+ opts
209
+ }
210
+
211
+ ...
212
+
213
+ foo.options(bar: :baz, one: 1, many: 2)
214
+ ```
215
+
216
+ As is the Ruby idiom of variable-length argument lists. The constant `ALL` as the last parameter
217
+ will match one or more arguments and pass them to the block as an array:
218
+
219
+ ```ruby
220
+ defn(:baz, Integer, ALL) { |int, args|
221
+ [int, args]
222
+ }
223
+ defn(:baz, ALL) { |args|
224
+ args
225
+ }
226
+ ```
227
+
228
+ Superclass polymorphism is supported as well. If an object cannot match a method
229
+ signature it will defer to the parent class:
230
+
231
+ ```ruby
232
+ class Bar
233
+ def greet
234
+ return 'Hello, World!'
235
+ end
236
+ end
237
+
238
+ class Foo < Bar
239
+ include PatternMatching
240
+
241
+ defn(:greet, _) do |name|
242
+ "Hello, #{name}!"
243
+ end
244
+ end
245
+
246
+ ...
247
+
248
+ foo.greet('Jerry') #=> "Hello, Jerry!"
249
+ foo.greet #=> "Hello, World!"
250
+ ```
251
+
252
+ Guard clauses in Erlang are defined with `when` clauses between the parameter list and the function body.
253
+ In Ruby, guard clauses are defined by chaining a call to `when` onto the the `defn` call and passing
254
+ a block. If the guard clause evaluates to true then the function will match. If the guard evaluates
255
+ to false the function will not match and pattern matching will continue:
256
+
257
+ Erlang:
258
+
259
+ ```erlang
260
+ old_enough(X) when X >= 16 -> true;
261
+ old_enough(_) -> false.
262
+ ```
263
+
264
+ Ruby:
265
+
266
+ ```ruby
267
+ defn(:old_enough, _){ true }.when{|x| x >= 16 }
268
+ defn(:old_enough, _){ false }
269
+ ```
270
+
271
+ ### Order Matters
272
+
273
+ As with Erlang, the order of pattern matches is significant. Patterns will be matched
274
+ *in the order declared* and the first match will be used. If a particular function call
275
+ can be matched by more than one pattern, the *first matched pattern* will be used. It
276
+ is the programmer's responsibility to ensure patterns are declared in the correct order.
277
+
278
+ ### Blocks and Procs and Lambdas, oh my!
279
+
280
+ When using this gem it is critical to remember that `defn` takes a block and
281
+ that blocks in Ruby have special rules. There are [plenty](https://www.google.com/search?q=ruby+block+proc+lambda)
282
+ of good tutorials on the web explaining [blocks](http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/)
283
+ and [Procs](https://coderwall.com/p/_-_mha) and [lambdas](http://railsguru.org/2010/03/learn-ruby-procs-blocks-lambda/)
284
+ in Ruby. Please read them. Please don't submit a bug report if you use a
285
+ `return` statement within your `defn` and your code blows up with a
286
+ [LocalJumpError](http://ruby-doc.org/core-2.0/LocalJumpError.html).
287
+
288
+ ## Examples
289
+
290
+ For more examples see the integration tests in *spec/integration_spec.rb*.
291
+
292
+ ### Simple Functions
293
+
294
+ This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
295
+
296
+ Erlang:
297
+
298
+ ```erlang
299
+ greet(male, Name) ->
300
+ io:format("Hello, Mr. ~s!", [Name]);
301
+ greet(female, Name) ->
302
+ io:format("Hello, Mrs. ~s!", [Name]);
303
+ greet(_, Name) ->
304
+ io:format("Hello, ~s!", [Name]).
305
+ ```
306
+
307
+ Ruby:
308
+
309
+ ```ruby
310
+ require 'pattern_matching'
311
+
312
+ class Foo
313
+ include PatternMatching
314
+
315
+ defn(:greet, _) do |name|
316
+ "Hello, #{name}!"
317
+ end
318
+
319
+ defn(:greet, :male, _) { |name|
320
+ "Hello, Mr. #{name}!"
321
+ }
322
+ defn(:greet, :female, _) { |name|
323
+ "Hello, Ms. #{name}!"
324
+ }
325
+ defn(:greet, _, _) { |_, name|
326
+ "Hello, #{name}!"
327
+ }
328
+ end
329
+ ```
330
+
331
+ ### Simple Functions with Overloading
332
+
333
+ This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
334
+
335
+ Erlang:
336
+
337
+ ```erlang
338
+ greet(Name) ->
339
+ io:format("Hello, ~s!", [Name]).
340
+
341
+ greet(male, Name) ->
342
+ io:format("Hello, Mr. ~s!", [Name]);
343
+ greet(female, Name) ->
344
+ io:format("Hello, Mrs. ~s!", [Name]);
345
+ greet(_, Name) ->
346
+ io:format("Hello, ~s!", [Name]).
347
+ ```
348
+
349
+ Ruby:
350
+
351
+ ```ruby
352
+ require 'pattern_matching'
353
+
354
+ class Foo
355
+ include PatternMatching
356
+
357
+ defn(:greet, _) do |name|
358
+ "Hello, #{name}!"
359
+ end
360
+
361
+ defn(:greet, :male, _) { |name|
362
+ "Hello, Mr. #{name}!"
363
+ }
364
+ defn(:greet, :female, _) { |name|
365
+ "Hello, Ms. #{name}!"
366
+ }
367
+ defn(:greet, nil, _) { |name|
368
+ "Goodbye, #{name}!"
369
+ }
370
+ defn(:greet, _, _) { |_, name|
371
+ "Hello, #{name}!"
372
+ }
373
+ end
374
+ ```
375
+
376
+ ### Matching by Class/Datatype
377
+
378
+ ```ruby
379
+ require 'pattern_matching'
380
+
381
+ class Foo
382
+ include PatternMatching
383
+
384
+ defn(:concat, Integer, Integer) { |first, second|
385
+ first + second
386
+ }
387
+ defn(:concat, Integer, String) { |first, second|
388
+ "#{first} #{second}"
389
+ }
390
+ defn(:concat, String, String) { |first, second|
391
+ first + second
392
+ }
393
+ defn(:concat, Integer, _) { |first, second|
394
+ first + second.to_i
395
+ }
396
+ end
397
+ ```
398
+
399
+ ### Matching a Hash Parameter
400
+
401
+ ```ruby
402
+ require 'pattern_matching'
403
+
404
+ class Foo
405
+ include PatternMatching
406
+
407
+ defn(:hashable, {foo: :bar}) { |opts|
408
+ # matches any hash with key :foo and value :bar
409
+ :foo_bar
410
+ }
411
+ defn(:hashable, {foo: _, bar: _}) { |f, b|
412
+ # matches any hash with keys :foo and :bar
413
+ # passes the values associated with those keys to the block
414
+ [f, b]
415
+ }
416
+ defn(:hashable, {foo: _}) { |f|
417
+ # matches any hash with key :foo
418
+ # passes the value associated with that key to the block
419
+ # must appear AFTER the prior match or it will override that one
420
+ f
421
+ }
422
+ defn(:hashable, {}) { ||
423
+ # matches an empty hash
424
+ :empty
425
+ }
426
+ defn(:hashable, _) { |opts|
427
+ # matches any hash (or any other value)
428
+ opts
429
+ }
430
+ end
431
+
432
+ ...
433
+
434
+ foo.hashable({foo: :bar}) #=> :foo_bar
435
+ foo.hashable({foo: :baz}) #=> :baz
436
+ foo.hashable({foo: 1, bar: 2}) #=> [1, 2]
437
+ foo.hashable({foo: 1, baz: 2}) #=> 1
438
+ foo.hashable({bar: :baz}) #=> {bar: :baz}
439
+ foo.hashable({}) #=> :empty
440
+ ```
441
+
442
+ ### Variable Length Argument Lists with ALL
443
+
444
+ ```ruby
445
+ defn(:all, :one, ALL) { |args|
446
+ args
447
+ }
448
+ defn(:all, :one, Integer, ALL) { |int, args|
449
+ [int, args]
450
+ }
451
+ defn(:all, 1, _, ALL) { |var, args|
452
+ [var, args]
453
+ }
454
+ defn(:all, ALL) { | args|
455
+ args
456
+ }
457
+
458
+ ...
459
+
460
+ foo.all(:one, 'a', 'bee', :see) #=> ['a', 'bee', :see]
461
+ foo.all(:one, 1, 'bee', :see) #=> [1, 'bee', :see]
462
+ foo.all(1, 'a', 'bee', :see) #=> ['a', ['bee', :see]]
463
+ foo.all('a', 'bee', :see) #=> ['a', 'bee', :see]
464
+ foo.all() #=> NoMethodError: no method `all` matching [] found for class Foo
465
+ ```
466
+
467
+ ### Guard Clauses
468
+
469
+ These examples are based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
470
+
471
+ Erlang:
472
+
473
+ ```erlang
474
+ old_enough(X) when X >= 16 -> true;
475
+ old_enough(_) -> false.
476
+
477
+ right_age(X) when X >= 16, X =< 104 ->
478
+ true;
479
+ right_age(_) ->
480
+ false.
481
+
482
+ wrong_age(X) when X < 16; X > 104 ->
483
+ true;
484
+ wrong_age(_) ->
485
+ false.
486
+ ```
487
+
488
+ ```ruby
489
+ defn(:old_enough, _){ true }.when{|x| x >= 16 }
490
+ defn(:old_enough, _){ false }
491
+
492
+ defn(:right_age, _) {
493
+ true
494
+ }.when{|x| x >= 16 && x <= 104 }
495
+
496
+ defn(:right_age, _) {
497
+ false
498
+ }
499
+
500
+ defn(:wrong_age, _) {
501
+ false
502
+ }.when{|x| x < 16 || x > 104 }
503
+
504
+ defn(:wrong_age, _) {
505
+ true
506
+ }
507
+ ```
508
+
509
+ ## Copyright
510
+
511
+ *PatternMatching* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
512
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
513
+
514
+ ## License
515
+
516
+ Released under the MIT license.
517
+
518
+ http://www.opensource.org/licenses/mit-license.php
519
+
520
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
521
+ > of this software and associated documentation files (the "Software"), to deal
522
+ > in the Software without restriction, including without limitation the rights
523
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
524
+ > copies of the Software, and to permit persons to whom the Software is
525
+ > furnished to do so, subject to the following conditions:
526
+ >
527
+ > The above copyright notice and this permission notice shall be included in
528
+ > all copies or substantial portions of the Software.
529
+ >
530
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
531
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
532
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
533
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
534
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
535
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
536
+ > THE SOFTWARE.