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.
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
+