functional-ruby 0.5.0 → 0.6.0

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