functional-ruby 0.7.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -152
  3. data/doc/memo.txt +192 -0
  4. data/doc/pattern_matching.txt +485 -0
  5. data/doc/protocol.txt +221 -0
  6. data/doc/record.txt +144 -0
  7. data/doc/thread_safety.txt +8 -0
  8. data/lib/functional.rb +48 -18
  9. data/lib/functional/abstract_struct.rb +161 -0
  10. data/lib/functional/delay.rb +117 -0
  11. data/lib/functional/either.rb +222 -0
  12. data/lib/functional/memo.rb +93 -0
  13. data/lib/functional/method_signature.rb +72 -0
  14. data/lib/functional/option.rb +209 -0
  15. data/lib/functional/pattern_matching.rb +117 -100
  16. data/lib/functional/protocol.rb +157 -0
  17. data/lib/functional/protocol_info.rb +193 -0
  18. data/lib/functional/record.rb +155 -0
  19. data/lib/functional/type_check.rb +112 -0
  20. data/lib/functional/union.rb +152 -0
  21. data/lib/functional/version.rb +3 -1
  22. data/spec/functional/abstract_struct_shared.rb +154 -0
  23. data/spec/functional/complex_pattern_matching_spec.rb +205 -0
  24. data/spec/functional/configuration_spec.rb +17 -0
  25. data/spec/functional/delay_spec.rb +147 -0
  26. data/spec/functional/either_spec.rb +237 -0
  27. data/spec/functional/memo_spec.rb +207 -0
  28. data/spec/functional/option_spec.rb +292 -0
  29. data/spec/functional/pattern_matching_spec.rb +279 -276
  30. data/spec/functional/protocol_info_spec.rb +444 -0
  31. data/spec/functional/protocol_spec.rb +274 -0
  32. data/spec/functional/record_spec.rb +175 -0
  33. data/spec/functional/type_check_spec.rb +103 -0
  34. data/spec/functional/union_spec.rb +110 -0
  35. data/spec/spec_helper.rb +6 -4
  36. metadata +55 -45
  37. data/lib/functional/behavior.rb +0 -138
  38. data/lib/functional/behaviour.rb +0 -2
  39. data/lib/functional/catalog.rb +0 -487
  40. data/lib/functional/collection.rb +0 -403
  41. data/lib/functional/inflect.rb +0 -127
  42. data/lib/functional/platform.rb +0 -120
  43. data/lib/functional/search.rb +0 -132
  44. data/lib/functional/sort.rb +0 -41
  45. data/lib/functional/utilities.rb +0 -189
  46. data/md/behavior.md +0 -188
  47. data/md/catalog.md +0 -32
  48. data/md/collection.md +0 -32
  49. data/md/inflect.md +0 -32
  50. data/md/pattern_matching.md +0 -512
  51. data/md/platform.md +0 -32
  52. data/md/search.md +0 -32
  53. data/md/sort.md +0 -32
  54. data/md/utilities.md +0 -55
  55. data/spec/functional/behavior_spec.rb +0 -528
  56. data/spec/functional/catalog_spec.rb +0 -1206
  57. data/spec/functional/collection_spec.rb +0 -752
  58. data/spec/functional/inflect_spec.rb +0 -85
  59. data/spec/functional/integration_spec.rb +0 -205
  60. data/spec/functional/platform_spec.rb +0 -501
  61. data/spec/functional/search_spec.rb +0 -187
  62. data/spec/functional/sort_spec.rb +0 -61
  63. data/spec/functional/utilities_spec.rb +0 -277
@@ -0,0 +1,485 @@
1
+ # @!macro [new] pattern_matching
2
+ #
3
+ # ## Features
4
+ #
5
+ # * Pattern matching for instance methods.
6
+ # * Pattern matching for object constructors.
7
+ # * Parameter count matching
8
+ # * Matching against primitive values
9
+ # * Matching by class/datatype
10
+ # * Matching against specific key/vaue pairs in hashes
11
+ # * Matching against the presence of keys within hashes
12
+ # * Implicit hash for last parameter
13
+ # * Variable-length parameter lists
14
+ # * Guard clauses
15
+ # * Recursive calls to other pattern matches
16
+ # * Recursive calls to superclass pattern matches
17
+ # * Recursive calls to superclass methods
18
+ # * Dispatching to superclass methods when no match is found
19
+ # * Reasonable error messages when no match is found
20
+ #
21
+ # ## Usage
22
+ #
23
+ # First, familiarize yourself with Erlang [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching).
24
+ # This gem may not make much sense if you don't understand how Erlang dispatches functions.
25
+ #
26
+ # In the Ruby class file where you want to use pattern matching, require the *functional-ruby* gem:
27
+ #
28
+ # ```ruby
29
+ # require 'functional'
30
+ # ```
31
+ #
32
+ # Then include `Functional::PatternMatching` in your class:
33
+ #
34
+ # ```ruby
35
+ # require 'functional'
36
+ #
37
+ # class Foo
38
+ # include Functional::PatternMatching
39
+ #
40
+ # ...
41
+ #
42
+ # end
43
+ # ```
44
+ #
45
+ # You can then define functions with `defn` instead of the normal *def* statement.
46
+ # The syntax for `defn` is:
47
+ #
48
+ # ```ruby
49
+ # defn(:symbol_name_of_function, zero, or, more, parameters) { |block, arguments|
50
+ # # code to execute
51
+ # }
52
+ # ```
53
+ # You can then call your new function just like any other:
54
+ #
55
+ # ```ruby
56
+ # require 'functional/pattern_matching'
57
+ #
58
+ # class Foo
59
+ # include Functional::PatternMatching
60
+ #
61
+ # defn(:hello) {
62
+ # puts "Hello, World!"
63
+ # }
64
+ # end
65
+ #
66
+ # foo = Foo.new
67
+ # foo.hello #=> "Hello, World!"
68
+ # ```
69
+ #
70
+ # Patterns to match against are included in the parameter list:
71
+ #
72
+ # ```ruby
73
+ # defn(:greet, :male) {
74
+ # puts "Hello, sir!"
75
+ # }
76
+ #
77
+ # defn(:greet, :female) {
78
+ # puts "Hello, ma'am!"
79
+ # }
80
+ #
81
+ # ...
82
+ #
83
+ # foo.greet(:male) #=> "Hello, sir!"
84
+ # foo.greet(:female) #=> "Hello, ma'am!"
85
+ # ```
86
+ #
87
+ # If a particular method call can not be matched a *NoMethodError* is thrown with
88
+ # a reasonably helpful error message:
89
+ #
90
+ # ```ruby
91
+ # foo.greet(:unknown) #=> NoMethodError: no method `greet` matching [:unknown] found for class Foo
92
+ # foo.greet #=> NoMethodError: no method `greet` matching [] found for class Foo
93
+ # ```
94
+ #
95
+ # Parameters that are expected to exist but that can take any value are considered
96
+ # *unbound* parameters. Unbound parameters are specified by the `_` underscore
97
+ # character or `UNBOUND`:
98
+ #
99
+ # ```ruby
100
+ # defn(:greet, _) do |name|
101
+ # "Hello, #{name}!"
102
+ # end
103
+ #
104
+ # defn(:greet, UNBOUND, UNBOUND) do |first, last|
105
+ # "Hello, #{first} #{last}!"
106
+ # end
107
+ #
108
+ # ...
109
+ #
110
+ # foo.greet('Jerry') #=> "Hello, Jerry!"
111
+ # ```
112
+ #
113
+ # All unbound parameters will be passed to the block in the order they are specified in the definition:
114
+ #
115
+ # ```ruby
116
+ # defn(:greet, _, _) do |first, last|
117
+ # "Hello, #{first} #{last}!"
118
+ # end
119
+ #
120
+ # ...
121
+ #
122
+ # foo.greet('Jerry', "D'Antonio") #=> "Hello, Jerry D'Antonio!"
123
+ # ```
124
+ #
125
+ # If for some reason you don't care about one or more unbound parameters within
126
+ # the block you can use the `_` underscore character in the block parameters list
127
+ # as well:
128
+ #
129
+ # ```ruby
130
+ # defn(:greet, _, _, _) do |first, _, last|
131
+ # "Hello, #{first} #{last}!"
132
+ # end
133
+ #
134
+ # ...
135
+ #
136
+ # foo.greet('Jerry', "I'm not going to tell you my middle name!", "D'Antonio") #=> "Hello, Jerry D'Antonio!"
137
+ # ```
138
+ #
139
+ # Hash parameters can match against specific keys and either bound or unbound parameters. This allows for
140
+ # function dispatch by hash parameters without having to dig through the hash:
141
+ #
142
+ # ```ruby
143
+ # defn(:hashable, {foo: :bar}) { |opts|
144
+ # :foo_bar
145
+ # }
146
+ # defn(:hashable, {foo: _}) { |f|
147
+ # f
148
+ # }
149
+ #
150
+ # ...
151
+ #
152
+ # foo.hashable({foo: :bar}) #=> :foo_bar
153
+ # foo.hashable({foo: :baz}) #=> :baz
154
+ # ```
155
+ #
156
+ # The Ruby idiom of the final parameter being a hash is also supported:
157
+ #
158
+ # ```ruby
159
+ # defn(:options, _) { |opts|
160
+ # opts
161
+ # }
162
+ #
163
+ # ...
164
+ #
165
+ # foo.options(bar: :baz, one: 1, many: 2)
166
+ # ```
167
+ #
168
+ # As is the Ruby idiom of variable-length argument lists. The constant `ALL` as the last parameter
169
+ # will match one or more arguments and pass them to the block as an array:
170
+ #
171
+ # ```ruby
172
+ # defn(:baz, Integer, ALL) { |int, args|
173
+ # [int, args]
174
+ # }
175
+ # defn(:baz, ALL) { |args|
176
+ # args
177
+ # }
178
+ # ```
179
+ #
180
+ # Superclass polymorphism is supported as well. If an object cannot match a method
181
+ # signature it will defer to the parent class:
182
+ #
183
+ # ```ruby
184
+ # class Bar
185
+ # def greet
186
+ # return 'Hello, World!'
187
+ # end
188
+ # end
189
+ #
190
+ # class Foo < Bar
191
+ # include Functional::PatternMatching
192
+ #
193
+ # defn(:greet, _) do |name|
194
+ # "Hello, #{name}!"
195
+ # end
196
+ # end
197
+ #
198
+ # ...
199
+ #
200
+ # foo.greet('Jerry') #=> "Hello, Jerry!"
201
+ # foo.greet #=> "Hello, World!"
202
+ # ```
203
+ #
204
+ # Guard clauses in Erlang are defined with `when` clauses between the parameter list and the function body.
205
+ # In Ruby, guard clauses are defined by chaining a call to `when` onto the the `defn` call and passing
206
+ # a block. If the guard clause evaluates to true then the function will match. If the guard evaluates
207
+ # to false the function will not match and pattern matching will continue:
208
+ #
209
+ # Erlang:
210
+ #
211
+ # ```erlang
212
+ # old_enough(X) when X >= 16 -> true;
213
+ # old_enough(_) -> false.
214
+ # ```
215
+ #
216
+ # Ruby:
217
+ #
218
+ # ```ruby
219
+ # defn(:old_enough, _){ true }.when{|x| x >= 16 }
220
+ # defn(:old_enough, _){ false }
221
+ # ```
222
+ #
223
+ # ### Order Matters
224
+ #
225
+ # As with Erlang, the order of pattern matches is significant. Patterns will be matched
226
+ # *in the order declared* and the first match will be used. If a particular function call
227
+ # can be matched by more than one pattern, the *first matched pattern* will be used. It
228
+ # is the programmer's responsibility to ensure patterns are declared in the correct order.
229
+ #
230
+ # ### Blocks and Procs and Lambdas, oh my!
231
+ #
232
+ # When using this gem it is critical to remember that `defn` takes a block and
233
+ # that blocks in Ruby have special rules. There are [plenty](https://www.google.com/search?q=ruby+block+proc+lambda)
234
+ # of good tutorials on the web explaining [blocks](http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/)
235
+ # and [Procs](https://coderwall.com/p/_-_mha) and [lambdas](http://railsguru.org/2010/03/learn-ruby-procs-blocks-lambda/)
236
+ # in Ruby. Please read them. Please don't submit a bug report if you use a
237
+ # `return` statement within your `defn` and your code blows up with a
238
+ # [LocalJumpError](http://ruby-doc.org/core-2.0/LocalJumpError.html).
239
+ #
240
+ # ### Examples
241
+ #
242
+ # For more examples see the integration tests in *spec/integration_spec.rb*.
243
+ #
244
+ # #### Simple Functions
245
+ #
246
+ # 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/).
247
+ #
248
+ # Erlang:
249
+ #
250
+ # ```erlang
251
+ # greet(male, Name) ->
252
+ # io:format("Hello, Mr. ~s!", [Name]);
253
+ # greet(female, Name) ->
254
+ # io:format("Hello, Mrs. ~s!", [Name]);
255
+ # greet(_, Name) ->
256
+ # io:format("Hello, ~s!", [Name]).
257
+ # ```
258
+ #
259
+ # Ruby:
260
+ #
261
+ # ```ruby
262
+ # require 'functional/pattern_matching'
263
+ #
264
+ # class Foo
265
+ # include Functional::PatternMatching
266
+ #
267
+ # defn(:greet, _) do |name|
268
+ # "Hello, #{name}!"
269
+ # end
270
+ #
271
+ # defn(:greet, :male, _) { |name|
272
+ # "Hello, Mr. #{name}!"
273
+ # }
274
+ # defn(:greet, :female, _) { |name|
275
+ # "Hello, Ms. #{name}!"
276
+ # }
277
+ # defn(:greet, _, _) { |_, name|
278
+ # "Hello, #{name}!"
279
+ # }
280
+ # end
281
+ # ```
282
+ #
283
+ # #### Simple Functions with Overloading
284
+ #
285
+ # 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/).
286
+ #
287
+ # Erlang:
288
+ #
289
+ # ```erlang
290
+ # greet(Name) ->
291
+ # io:format("Hello, ~s!", [Name]).
292
+ #
293
+ # greet(male, Name) ->
294
+ # io:format("Hello, Mr. ~s!", [Name]);
295
+ # greet(female, Name) ->
296
+ # io:format("Hello, Mrs. ~s!", [Name]);
297
+ # greet(_, Name) ->
298
+ # io:format("Hello, ~s!", [Name]).
299
+ # ```
300
+ #
301
+ # Ruby:
302
+ #
303
+ # ```ruby
304
+ # require 'functional/pattern_matching'
305
+ #
306
+ # class Foo
307
+ # include Functional::PatternMatching
308
+ #
309
+ # defn(:greet, _) do |name|
310
+ # "Hello, #{name}!"
311
+ # end
312
+ #
313
+ # defn(:greet, :male, _) { |name|
314
+ # "Hello, Mr. #{name}!"
315
+ # }
316
+ # defn(:greet, :female, _) { |name|
317
+ # "Hello, Ms. #{name}!"
318
+ # }
319
+ # defn(:greet, nil, _) { |name|
320
+ # "Goodbye, #{name}!"
321
+ # }
322
+ # defn(:greet, _, _) { |_, name|
323
+ # "Hello, #{name}!"
324
+ # }
325
+ # end
326
+ # ```
327
+ #
328
+ # #### Constructor Overloading
329
+ #
330
+ # ```ruby
331
+ # require 'functional/pattern_matching'
332
+ #
333
+ # class Foo
334
+ # include Functional::PatternMatching
335
+ #
336
+ # defn(:initialize) { @name = 'baz' }
337
+ # defn(:initialize, _) {|name| @name = name.to_s }
338
+ # end
339
+ # ```
340
+ #
341
+ # #### Matching by Class/Datatype
342
+ #
343
+ # ```ruby
344
+ # require 'functional/pattern_matching'
345
+ #
346
+ # class Foo
347
+ # include Functional::PatternMatching
348
+ #
349
+ # defn(:concat, Integer, Integer) { |first, second|
350
+ # first + second
351
+ # }
352
+ # defn(:concat, Integer, String) { |first, second|
353
+ # "#{first} #{second}"
354
+ # }
355
+ # defn(:concat, String, String) { |first, second|
356
+ # first + second
357
+ # }
358
+ # defn(:concat, Integer, _) { |first, second|
359
+ # first + second.to_i
360
+ # }
361
+ # end
362
+ # ```
363
+ #
364
+ # #### Matching a Hash Parameter
365
+ #
366
+ # ```ruby
367
+ # require 'functional/pattern_matching'
368
+ #
369
+ # class Foo
370
+ # include Functional::PatternMatching
371
+ #
372
+ # defn(:hashable, {foo: :bar}) { |opts|
373
+ # # matches any hash with key :foo and value :bar
374
+ # :foo_bar
375
+ # }
376
+ # defn(:hashable, {foo: _, bar: _}) { |f, b|
377
+ # # matches any hash with keys :foo and :bar
378
+ # # passes the values associated with those keys to the block
379
+ # [f, b]
380
+ # }
381
+ # defn(:hashable, {foo: _}) { |f|
382
+ # # matches any hash with key :foo
383
+ # # passes the value associated with that key to the block
384
+ # # must appear AFTER the prior match or it will override that one
385
+ # f
386
+ # }
387
+ # defn(:hashable, {}) { ||
388
+ # # matches an empty hash
389
+ # :empty
390
+ # }
391
+ # defn(:hashable, _) { |opts|
392
+ # # matches any hash (or any other value)
393
+ # opts
394
+ # }
395
+ # end
396
+ #
397
+ # ...
398
+ #
399
+ # foo.hashable({foo: :bar}) #=> :foo_bar
400
+ # foo.hashable({foo: :baz}) #=> :baz
401
+ # foo.hashable({foo: 1, bar: 2}) #=> [1, 2]
402
+ # foo.hashable({foo: 1, baz: 2}) #=> 1
403
+ # foo.hashable({bar: :baz}) #=> {bar: :baz}
404
+ # foo.hashable({}) #=> :empty
405
+ # ```
406
+ #
407
+ # #### Variable Length Argument Lists with ALL
408
+ #
409
+ # ```ruby
410
+ # defn(:all, :one, ALL) { |args|
411
+ # args
412
+ # }
413
+ # defn(:all, :one, Integer, ALL) { |int, args|
414
+ # [int, args]
415
+ # }
416
+ # defn(:all, 1, _, ALL) { |var, args|
417
+ # [var, args]
418
+ # }
419
+ # defn(:all, ALL) { | args|
420
+ # args
421
+ # }
422
+ #
423
+ # ...
424
+ #
425
+ # foo.all(:one, 'a', 'bee', :see) #=> ['a', 'bee', :see]
426
+ # foo.all(:one, 1, 'bee', :see) #=> [1, 'bee', :see]
427
+ # foo.all(1, 'a', 'bee', :see) #=> ['a', ['bee', :see]]
428
+ # foo.all('a', 'bee', :see) #=> ['a', 'bee', :see]
429
+ # foo.all() #=> NoMethodError: no method `all` matching [] found for class Foo
430
+ # ```
431
+ #
432
+ # #### Guard Clauses
433
+ #
434
+ # These examples are based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions)
435
+ # in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
436
+ #
437
+ # Erlang:
438
+ #
439
+ # ```erlang
440
+ # old_enough(X) when X >= 16 -> true;
441
+ # old_enough(_) -> false.
442
+ #
443
+ # right_age(X) when X >= 16, X =< 104 ->
444
+ # true;
445
+ # right_age(_) ->
446
+ # false.
447
+ #
448
+ # wrong_age(X) when X < 16; X > 104 ->
449
+ # true;
450
+ # wrong_age(_) ->
451
+ # false.
452
+ # ```
453
+ #
454
+ # ```ruby
455
+ # defn(:old_enough, _){ true }.when{|x| x >= 16 }
456
+ # defn(:old_enough, _){ false }
457
+ #
458
+ # defn(:right_age, _) {
459
+ # true
460
+ # }.when{|x| x >= 16 && x <= 104 }
461
+ #
462
+ # defn(:right_age, _) {
463
+ # false
464
+ # }
465
+ #
466
+ # defn(:wrong_age, _) {
467
+ # false
468
+ # }.when{|x| x < 16 || x > 104 }
469
+ #
470
+ # defn(:wrong_age, _) {
471
+ # true
472
+ # }
473
+ # ```
474
+ #
475
+ # ## Inspiration
476
+ #
477
+ # Pattern matching has its roots in logic programming languages such as
478
+ # [Prolog](http://en.wikipedia.org/wiki/Prolog). Pattern matching is a core
479
+ # feature of the [Erlang](http://www.erlang.org/) programming language. A few
480
+ # helpful resources are:
481
+ #
482
+ # * Erlang [modules](http://erlang.org/doc/reference_manual/modules.html)
483
+ # * Erlang [pattern matching](http://erlang.org/doc/reference_manual/patterns.html)
484
+
485
+