functional-ruby 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +154 -562
- data/lib/functional/agent.rb +130 -0
- data/lib/functional/all.rb +9 -1
- data/lib/functional/behavior.rb +72 -39
- data/lib/functional/cached_thread_pool.rb +122 -0
- data/lib/functional/concurrency.rb +32 -24
- data/lib/functional/core.rb +2 -62
- data/lib/functional/event.rb +53 -0
- data/lib/functional/event_machine_defer_proxy.rb +23 -0
- data/lib/functional/fixed_thread_pool.rb +89 -0
- data/lib/functional/future.rb +42 -0
- data/lib/functional/global_thread_pool.rb +3 -0
- data/lib/functional/obligation.rb +121 -0
- data/lib/functional/promise.rb +194 -0
- data/lib/functional/thread_pool.rb +61 -0
- data/lib/functional/utilities.rb +114 -0
- data/lib/functional/version.rb +1 -1
- data/lib/functional.rb +1 -0
- data/lib/functional_ruby.rb +1 -0
- data/md/behavior.md +147 -0
- data/md/concurrency.md +465 -0
- data/md/future.md +32 -0
- data/md/obligation.md +32 -0
- data/md/pattern_matching.md +512 -0
- data/md/promise.md +220 -0
- data/md/utilities.md +53 -0
- data/spec/functional/agent_spec.rb +405 -0
- data/spec/functional/behavior_spec.rb +12 -33
- data/spec/functional/cached_thread_pool_spec.rb +112 -0
- data/spec/functional/concurrency_spec.rb +55 -0
- data/spec/functional/event_machine_defer_proxy_spec.rb +246 -0
- data/spec/functional/event_spec.rb +114 -0
- data/spec/functional/fixed_thread_pool_spec.rb +84 -0
- data/spec/functional/future_spec.rb +115 -0
- data/spec/functional/obligation_shared.rb +121 -0
- data/spec/functional/pattern_matching_spec.rb +10 -8
- data/spec/functional/promise_spec.rb +310 -0
- data/spec/functional/thread_pool_shared.rb +209 -0
- data/spec/functional/utilities_spec.rb +149 -0
- data/spec/spec_helper.rb +2 -0
- metadata +55 -5
data/README.md
CHANGED
@@ -1,79 +1,77 @@
|
|
1
|
-
# Functional Ruby [](
|
1
|
+
# Functional Ruby [](https://travis-ci.org/jdantonio/functional-ruby?branch=master) [](https://gemnasium.com/jdantonio/functional-ruby)
|
2
2
|
|
3
|
-
A gem for adding Erlang and
|
3
|
+
A gem for adding Erlang, Clojure, and Go inspired concurrency and functional programming tools to Ruby.
|
4
4
|
|
5
5
|
The project is hosted on the following sites:
|
6
6
|
|
7
7
|
* [RubyGems project page](https://rubygems.org/gems/functional-ruby)
|
8
8
|
* [Source code on GitHub](https://github.com/jdantonio/functional-ruby)
|
9
|
+
* [YARD documentation on RubyDoc.info](http://rubydoc.info/github/jdantonio/functional-ruby/master/frames)
|
9
10
|
* [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/functional-ruby)
|
10
11
|
* [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/functional-ruby)
|
11
12
|
* [Follow me on Twitter](https://twitter.com/jerrydantonio)
|
12
13
|
|
13
14
|
## Introduction
|
14
15
|
|
15
|
-
[Ruby](http://www.ruby-lang.org/en/)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
for helping programmers write Ruby code in a functional style. So I changed the name of the gem
|
31
|
-
and kept on trucking.
|
16
|
+
Three things I love are [Ruby](http://www.ruby-lang.org/en/),
|
17
|
+
[functional](https://en.wikipedia.org/wiki/Functional_programming)
|
18
|
+
[programming](http://c2.com/cgi/wiki?FunctionalProgramming) and
|
19
|
+
[concurrency](http://www.amazon.com/s/ref=nb_sb_noss_1?url=search-alias%3Dstripbooks&field-keywords=concurrent%20programming).
|
20
|
+
Sadly, the first is generally not associated with the other two. First, I reject the
|
21
|
+
assertion that Ruby is an object-oriented language. It's certainly object-based, since
|
22
|
+
everything is an object, but entire large-scale programs can be built without ever
|
23
|
+
defining a single class. Ruby is a true multi-paradigm language and easily supports
|
24
|
+
many advanced functional techniques. As to concurrency, Ruby's bad reputation is
|
25
|
+
well earned, but recent versions of Ruby have made significan improvements in that
|
26
|
+
area. Ruby 2.0 is now a [relevant](https://blog.heroku.com/archives/2013/6/17/ruby-2-default-new-aps)
|
27
|
+
platform for concurrent applications.
|
28
|
+
|
29
|
+
This gem is my small and humble attempt to help Ruby reach its full potential as
|
30
|
+
a highly performant, functional, concurrent programming language.
|
32
31
|
|
33
32
|
### Goals
|
34
33
|
|
35
|
-
|
34
|
+
My history with high-performance, highly-concurrent programming goes back to my days with C/C++.
|
35
|
+
I have the same scars as everyone else doing that kind of work with those languages.
|
36
|
+
I'm fascinated by modern concurrency patterns like [Actors](http://en.wikipedia.org/wiki/Actor_model),
|
37
|
+
[Agents](http://doc.akka.io/docs/akka/snapshot/java/agents.html), and
|
38
|
+
[Promises](http://promises-aplus.github.io/promises-spec/). I'm equally fascinated by languages
|
39
|
+
with strong concurrency support like [Erlang](http://www.erlang.org/doc/getting_started/conc_prog.html),
|
40
|
+
[Go](http://golang.org/doc/articles/concurrency_patterns.html), and
|
41
|
+
[Clojure](http://clojure.org/concurrent_programming) (I program with Erlang at work).
|
42
|
+
My goal is to implement those patterns in Ruby. Specifically:
|
43
|
+
|
44
|
+
* Stay true to the spirit of the languages providing inspiration
|
45
|
+
* But implement in a way that makes sense for Ruby
|
36
46
|
* Keep the semantics as idiomatic Ruby as possible
|
37
47
|
* Support features that make sense in Ruby
|
38
|
-
* Exclude features that
|
39
|
-
* Avoid using *method_missing*
|
48
|
+
* Exclude features that don't make sense in Ruby
|
40
49
|
* Keep everything small
|
41
50
|
* Be as fast as reasonably possible
|
42
51
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
*
|
48
|
-
*
|
49
|
-
*
|
50
|
-
*
|
51
|
-
*
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
I would never advocate turning Ruby into the cesspool complex object creation that Java has
|
67
|
-
unfortunately become, but occasionally it would be nice to make sure a class implements a set of
|
68
|
-
required methods. Enter Erlang's [-behavior](http://metajack.im/2008/10/29/custom-behaviors-in-erlang/)
|
69
|
-
keyword. Basically, you define a `behavior_info` then drop a `behavior` call within a class.
|
70
|
-
Forget to implement a required method and Ruby will let you know. See the examples below for details.
|
71
|
-
|
72
|
-
## Supported Ruby versions
|
73
|
-
|
74
|
-
MRI 1.9.x and above. Anything else and your mileage may vary.
|
75
|
-
|
76
|
-
## Install
|
52
|
+
## Features (and Documentation)
|
53
|
+
|
54
|
+
Several features from Erlang, Co, Clojure, and JavaScript have been implemented this far:
|
55
|
+
|
56
|
+
* Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
|
57
|
+
* Interface specifications with Erlang-style [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
|
58
|
+
* Chained asynchronous operations inspried by JavaScript [Promises](https://github.com/jdantonio/functional-ruby/blob/master/md/promise.md)
|
59
|
+
* Additional Clojure, Go, and Erlang inspired [Concurrency](https://github.com/jdantonio/functional-ruby/blob/master/md/concurrency.md)
|
60
|
+
* Several useful functional [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
|
61
|
+
|
62
|
+
### Is it any good?
|
63
|
+
|
64
|
+
[Yes](http://news.ycombinator.com/item?id=3067434)
|
65
|
+
|
66
|
+
### Supported Ruby versions
|
67
|
+
|
68
|
+
MRI 1.9.2, 1.9.3, and 2.0. This library is pure Ruby and has no gem dependencies. It should be
|
69
|
+
fully compatible with any Ruby interpreter that is 1.9.x compliant. I simply don't know enough
|
70
|
+
about JRuby, Rubinius, or the others to fully support them. I can promise good karma and
|
71
|
+
attribution on this page to anyone wishing to take responsibility for verifying compaitibility
|
72
|
+
with any Ruby other than MRI.
|
73
|
+
|
74
|
+
### Install
|
77
75
|
|
78
76
|
```shell
|
79
77
|
gem install functional-ruby
|
@@ -93,8 +91,10 @@ that not all users may want, several `require` options are available:
|
|
93
91
|
```ruby
|
94
92
|
require 'functional/behavior'
|
95
93
|
require 'functional/behaviour' # alternate spelling
|
94
|
+
require 'functional/concurrency'
|
96
95
|
require 'functional/pattern_matching'
|
97
|
-
require 'functional/
|
96
|
+
require 'functional/promise'
|
97
|
+
require 'functional/utilities'
|
98
98
|
```
|
99
99
|
|
100
100
|
If you want everything you can do that, too:
|
@@ -103,18 +103,14 @@ If you want everything you can do that, too:
|
|
103
103
|
require 'functional/all'
|
104
104
|
```
|
105
105
|
|
106
|
-
##
|
106
|
+
## Examples
|
107
107
|
|
108
|
-
|
109
|
-
|
108
|
+
For complete examples, see the specific documentation linked above. Below are a
|
109
|
+
few examples to whet your appetite.
|
110
110
|
|
111
|
-
|
111
|
+
### Pattern Matching (Erlang)
|
112
112
|
|
113
|
-
|
114
|
-
require 'functional/pattern_matching'
|
115
|
-
```
|
116
|
-
|
117
|
-
Then include `PatternMatching` in your class:
|
113
|
+
Documentation: [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
|
118
114
|
|
119
115
|
```ruby
|
120
116
|
require 'functional/pattern_matching'
|
@@ -122,548 +118,144 @@ require 'functional/pattern_matching'
|
|
122
118
|
class Foo
|
123
119
|
include PatternMatching
|
124
120
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
```
|
129
|
-
|
130
|
-
You can then define functions with `defn` instead of the normal *def* statement.
|
131
|
-
The syntax for `defn` is:
|
132
|
-
|
133
|
-
```ruby
|
134
|
-
defn(:symbol_name_of_function, zero, or, more, parameters) { |block, arguments|
|
135
|
-
# code to execute
|
136
|
-
}
|
137
|
-
```
|
138
|
-
You can then call your new function just like any other:
|
139
|
-
|
140
|
-
```ruby
|
141
|
-
require 'functional/pattern_matching'
|
142
|
-
|
143
|
-
class Foo
|
144
|
-
include PatternMatching
|
121
|
+
defn(:greet, :male) {
|
122
|
+
puts "Hello, sir!"
|
123
|
+
}
|
145
124
|
|
146
|
-
defn(:
|
147
|
-
puts "Hello,
|
125
|
+
defn(:greet, :female) {
|
126
|
+
puts "Hello, ma'am!"
|
148
127
|
}
|
149
128
|
end
|
150
129
|
|
151
130
|
foo = Foo.new
|
152
|
-
foo.
|
153
|
-
|
154
|
-
|
155
|
-
Patterns to match against are included in the parameter list:
|
156
|
-
|
157
|
-
```ruby
|
158
|
-
defn(:greet, :male) {
|
159
|
-
puts "Hello, sir!"
|
160
|
-
}
|
161
|
-
|
162
|
-
defn(:greet, :female) {
|
163
|
-
puts "Hello, ma'am!"
|
164
|
-
}
|
165
|
-
|
166
|
-
...
|
167
|
-
|
168
|
-
foo.hello(:male) #=> "Hello, sir!"
|
169
|
-
foo.hello(:female) #=> "Hello, ma'am!"
|
170
|
-
```
|
171
|
-
|
172
|
-
If a particular method call can not be matched a *NoMethodError* is thrown with
|
173
|
-
a reasonably helpful error message:
|
174
|
-
|
175
|
-
```ruby
|
176
|
-
foo.greet(:unknown) #=> NoMethodError: no method `greet` matching [:unknown] found for class Foo
|
177
|
-
foo.greet #=> NoMethodError: no method `greet` matching [] found for class Foo
|
178
|
-
```
|
179
|
-
|
180
|
-
Parameters that are expected to exist but that can take any value are considered
|
181
|
-
*unbound* parameters. Unbound parameters are specified by the `_` underscore
|
182
|
-
character or `UNBOUND`:
|
183
|
-
|
184
|
-
```ruby
|
185
|
-
defn(:greet, _) do |name|
|
186
|
-
"Hello, #{name}!"
|
187
|
-
end
|
188
|
-
|
189
|
-
defn(:greet, UNBOUND, UNBOUND) do |first, last|
|
190
|
-
"Hello, #{first} #{last}!"
|
191
|
-
end
|
192
|
-
|
193
|
-
...
|
194
|
-
|
195
|
-
foo.greet('Jerry') #=> "Hello, Jerry!"
|
131
|
+
foo.greet(:male) #=> "Hello, sir!"
|
132
|
+
foo.greet(:female) #=> "Hello, ma'am!"
|
196
133
|
```
|
197
134
|
|
198
|
-
|
199
|
-
|
200
|
-
```ruby
|
201
|
-
defn(:greet, _, _) do |first, last|
|
202
|
-
"Hello, #{first} #{last}!"
|
203
|
-
end
|
204
|
-
|
205
|
-
...
|
206
|
-
|
207
|
-
foo.greet('Jerry', "D'Antonio") #=> "Hello, Jerry D'Antonio!"
|
208
|
-
```
|
209
|
-
|
210
|
-
If for some reason you don't care about one or more unbound parameters within
|
211
|
-
the block you can use the `_` underscore character in the block parameters list
|
212
|
-
as well:
|
213
|
-
|
214
|
-
```ruby
|
215
|
-
defn(:greet, _, _, _) do |first, _, last|
|
216
|
-
"Hello, #{first} #{last}!"
|
217
|
-
end
|
218
|
-
|
219
|
-
...
|
220
|
-
|
221
|
-
foo.greet('Jerry', "I'm not going to tell you my middle name!", "D'Antonio") #=> "Hello, Jerry D'Antonio!"
|
222
|
-
```
|
135
|
+
### Behavior (Erlang)
|
223
136
|
|
224
|
-
|
225
|
-
function dispatch by hash parameters without having to dig through the hash:
|
137
|
+
Documentation: [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
|
226
138
|
|
227
139
|
```ruby
|
228
|
-
|
229
|
-
:foo_bar
|
230
|
-
}
|
231
|
-
defn(:hashable, {foo: _}) { |f|
|
232
|
-
f
|
233
|
-
}
|
234
|
-
|
235
|
-
...
|
236
|
-
|
237
|
-
foo.hashable({foo: :bar}) #=> :foo_bar
|
238
|
-
foo.hashable({foo: :baz}) #=> :baz
|
239
|
-
```
|
240
|
-
|
241
|
-
The Ruby idiom of the final parameter being a hash is also supported:
|
242
|
-
|
243
|
-
```ruby
|
244
|
-
defn(:options, _) { |opts|
|
245
|
-
opts
|
246
|
-
}
|
247
|
-
|
248
|
-
...
|
249
|
-
|
250
|
-
foo.options(bar: :baz, one: 1, many: 2)
|
251
|
-
```
|
252
|
-
|
253
|
-
As is the Ruby idiom of variable-length argument lists. The constant `ALL` as the last parameter
|
254
|
-
will match one or more arguments and pass them to the block as an array:
|
255
|
-
|
256
|
-
```ruby
|
257
|
-
defn(:baz, Integer, ALL) { |int, args|
|
258
|
-
[int, args]
|
259
|
-
}
|
260
|
-
defn(:baz, ALL) { |args|
|
261
|
-
args
|
262
|
-
}
|
263
|
-
```
|
264
|
-
|
265
|
-
Superclass polymorphism is supported as well. If an object cannot match a method
|
266
|
-
signature it will defer to the parent class:
|
267
|
-
|
268
|
-
```ruby
|
269
|
-
class Bar
|
270
|
-
def greet
|
271
|
-
return 'Hello, World!'
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
class Foo < Bar
|
276
|
-
include PatternMatching
|
277
|
-
|
278
|
-
defn(:greet, _) do |name|
|
279
|
-
"Hello, #{name}!"
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
...
|
284
|
-
|
285
|
-
foo.greet('Jerry') #=> "Hello, Jerry!"
|
286
|
-
foo.greet #=> "Hello, World!"
|
287
|
-
```
|
288
|
-
|
289
|
-
Guard clauses in Erlang are defined with `when` clauses between the parameter list and the function body.
|
290
|
-
In Ruby, guard clauses are defined by chaining a call to `when` onto the the `defn` call and passing
|
291
|
-
a block. If the guard clause evaluates to true then the function will match. If the guard evaluates
|
292
|
-
to false the function will not match and pattern matching will continue:
|
293
|
-
|
294
|
-
Erlang:
|
295
|
-
|
296
|
-
```erlang
|
297
|
-
old_enough(X) when X >= 16 -> true;
|
298
|
-
old_enough(_) -> false.
|
299
|
-
```
|
300
|
-
|
301
|
-
Ruby:
|
302
|
-
|
303
|
-
```ruby
|
304
|
-
defn(:old_enough, _){ true }.when{|x| x >= 16 }
|
305
|
-
defn(:old_enough, _){ false }
|
306
|
-
```
|
307
|
-
|
308
|
-
### Order Matters
|
309
|
-
|
310
|
-
As with Erlang, the order of pattern matches is significant. Patterns will be matched
|
311
|
-
*in the order declared* and the first match will be used. If a particular function call
|
312
|
-
can be matched by more than one pattern, the *first matched pattern* will be used. It
|
313
|
-
is the programmer's responsibility to ensure patterns are declared in the correct order.
|
314
|
-
|
315
|
-
### Blocks and Procs and Lambdas, oh my!
|
316
|
-
|
317
|
-
When using this gem it is critical to remember that `defn` takes a block and
|
318
|
-
that blocks in Ruby have special rules. There are [plenty](https://www.google.com/search?q=ruby+block+proc+lambda)
|
319
|
-
of good tutorials on the web explaining [blocks](http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/)
|
320
|
-
and [Procs](https://coderwall.com/p/_-_mha) and [lambdas](http://railsguru.org/2010/03/learn-ruby-procs-blocks-lambda/)
|
321
|
-
in Ruby. Please read them. Please don't submit a bug report if you use a
|
322
|
-
`return` statement within your `defn` and your code blows up with a
|
323
|
-
[LocalJumpError](http://ruby-doc.org/core-2.0/LocalJumpError.html).
|
324
|
-
|
325
|
-
### Examples
|
326
|
-
|
327
|
-
For more examples see the integration tests in *spec/integration_spec.rb*.
|
328
|
-
|
329
|
-
#### Simple Functions
|
330
|
-
|
331
|
-
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/).
|
332
|
-
|
333
|
-
Erlang:
|
334
|
-
|
335
|
-
```erlang
|
336
|
-
greet(male, Name) ->
|
337
|
-
io:format("Hello, Mr. ~s!", [Name]);
|
338
|
-
greet(female, Name) ->
|
339
|
-
io:format("Hello, Mrs. ~s!", [Name]);
|
340
|
-
greet(_, Name) ->
|
341
|
-
io:format("Hello, ~s!", [Name]).
|
342
|
-
```
|
343
|
-
|
344
|
-
Ruby:
|
140
|
+
require 'functional/behavior'
|
345
141
|
|
346
|
-
|
347
|
-
require 'functional/pattern_matching'
|
142
|
+
behaviour_info(:gen_foo, foo: 0, bar: 1)
|
348
143
|
|
349
144
|
class Foo
|
350
|
-
|
145
|
+
behavior(:gen_foo)
|
351
146
|
|
352
|
-
|
353
|
-
|
147
|
+
def foo
|
148
|
+
return 'foo/0'
|
354
149
|
end
|
355
150
|
|
356
|
-
|
357
|
-
|
358
|
-
}
|
359
|
-
defn(:greet, :female, _) { |name|
|
360
|
-
"Hello, Ms. #{name}!"
|
361
|
-
}
|
362
|
-
defn(:greet, _, _) { |_, name|
|
363
|
-
"Hello, #{name}!"
|
364
|
-
}
|
365
|
-
end
|
366
|
-
```
|
367
|
-
|
368
|
-
#### Simple Functions with Overloading
|
369
|
-
|
370
|
-
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/).
|
371
|
-
|
372
|
-
Erlang:
|
373
|
-
|
374
|
-
```erlang
|
375
|
-
greet(Name) ->
|
376
|
-
io:format("Hello, ~s!", [Name]).
|
377
|
-
|
378
|
-
greet(male, Name) ->
|
379
|
-
io:format("Hello, Mr. ~s!", [Name]);
|
380
|
-
greet(female, Name) ->
|
381
|
-
io:format("Hello, Mrs. ~s!", [Name]);
|
382
|
-
greet(_, Name) ->
|
383
|
-
io:format("Hello, ~s!", [Name]).
|
384
|
-
```
|
385
|
-
|
386
|
-
Ruby:
|
387
|
-
|
388
|
-
```ruby
|
389
|
-
require 'functional/pattern_matching'
|
390
|
-
|
391
|
-
class Foo
|
392
|
-
include PatternMatching
|
393
|
-
|
394
|
-
defn(:greet, _) do |name|
|
395
|
-
"Hello, #{name}!"
|
151
|
+
def bar(one, &block)
|
152
|
+
return 'bar/1'
|
396
153
|
end
|
397
|
-
|
398
|
-
defn(:greet, :male, _) { |name|
|
399
|
-
"Hello, Mr. #{name}!"
|
400
|
-
}
|
401
|
-
defn(:greet, :female, _) { |name|
|
402
|
-
"Hello, Ms. #{name}!"
|
403
|
-
}
|
404
|
-
defn(:greet, nil, _) { |name|
|
405
|
-
"Goodbye, #{name}!"
|
406
|
-
}
|
407
|
-
defn(:greet, _, _) { |_, name|
|
408
|
-
"Hello, #{name}!"
|
409
|
-
}
|
410
|
-
end
|
411
|
-
```
|
412
|
-
|
413
|
-
#### Constructor Overloading
|
414
|
-
|
415
|
-
```ruby
|
416
|
-
require 'functional/pattern_matching'
|
417
|
-
|
418
|
-
class Foo
|
419
|
-
include PatternMatching
|
420
|
-
|
421
|
-
defn(:initialize) { @name = 'baz' }
|
422
|
-
defn(:initialize, _) {|name| @name = name.to_s }
|
423
154
|
end
|
424
|
-
```
|
425
|
-
|
426
|
-
#### Matching by Class/Datatype
|
427
|
-
|
428
|
-
```ruby
|
429
|
-
require 'functional/pattern_matching'
|
430
|
-
|
431
|
-
class Foo
|
432
|
-
include PatternMatching
|
433
|
-
|
434
|
-
defn(:concat, Integer, Integer) { |first, second|
|
435
|
-
first + second
|
436
|
-
}
|
437
|
-
defn(:concat, Integer, String) { |first, second|
|
438
|
-
"#{first} #{second}"
|
439
|
-
}
|
440
|
-
defn(:concat, String, String) { |first, second|
|
441
|
-
first + second
|
442
|
-
}
|
443
|
-
defn(:concat, Integer, _) { |first, second|
|
444
|
-
first + second.to_i
|
445
|
-
}
|
446
|
-
end
|
447
|
-
```
|
448
155
|
|
449
|
-
|
450
|
-
|
451
|
-
```ruby
|
452
|
-
require 'functional/pattern_matching'
|
453
|
-
|
454
|
-
class Foo
|
455
|
-
include PatternMatching
|
456
|
-
|
457
|
-
defn(:hashable, {foo: :bar}) { |opts|
|
458
|
-
# matches any hash with key :foo and value :bar
|
459
|
-
:foo_bar
|
460
|
-
}
|
461
|
-
defn(:hashable, {foo: _, bar: _}) { |f, b|
|
462
|
-
# matches any hash with keys :foo and :bar
|
463
|
-
# passes the values associated with those keys to the block
|
464
|
-
[f, b]
|
465
|
-
}
|
466
|
-
defn(:hashable, {foo: _}) { |f|
|
467
|
-
# matches any hash with key :foo
|
468
|
-
# passes the value associated with that key to the block
|
469
|
-
# must appear AFTER the prior match or it will override that one
|
470
|
-
f
|
471
|
-
}
|
472
|
-
defn(:hashable, {}) { ||
|
473
|
-
# matches an empty hash
|
474
|
-
:empty
|
475
|
-
}
|
476
|
-
defn(:hashable, _) { |opts|
|
477
|
-
# matches any hash (or any other value)
|
478
|
-
opts
|
479
|
-
}
|
480
|
-
end
|
481
|
-
|
482
|
-
...
|
483
|
-
|
484
|
-
foo.hashable({foo: :bar}) #=> :foo_bar
|
485
|
-
foo.hashable({foo: :baz}) #=> :baz
|
486
|
-
foo.hashable({foo: 1, bar: 2}) #=> [1, 2]
|
487
|
-
foo.hashable({foo: 1, baz: 2}) #=> 1
|
488
|
-
foo.hashable({bar: :baz}) #=> {bar: :baz}
|
489
|
-
foo.hashable({}) #=> :empty
|
490
|
-
```
|
491
|
-
|
492
|
-
#### Variable Length Argument Lists with ALL
|
156
|
+
foo = Foo.new
|
493
157
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
}
|
498
|
-
defn(:all, :one, Integer, ALL) { |int, args|
|
499
|
-
[int, args]
|
500
|
-
}
|
501
|
-
defn(:all, 1, _, ALL) { |var, args|
|
502
|
-
[var, args]
|
503
|
-
}
|
504
|
-
defn(:all, ALL) { | args|
|
505
|
-
args
|
506
|
-
}
|
507
|
-
|
508
|
-
...
|
509
|
-
|
510
|
-
foo.all(:one, 'a', 'bee', :see) #=> ['a', 'bee', :see]
|
511
|
-
foo.all(:one, 1, 'bee', :see) #=> [1, 'bee', :see]
|
512
|
-
foo.all(1, 'a', 'bee', :see) #=> ['a', ['bee', :see]]
|
513
|
-
foo.all('a', 'bee', :see) #=> ['a', 'bee', :see]
|
514
|
-
foo.all() #=> NoMethodError: no method `all` matching [] found for class Foo
|
158
|
+
foo.behaves_as? :gen_foo #=> true
|
159
|
+
foo.behaves_as?(:bogus) #=> false
|
160
|
+
'foo'.behaves_as? :gen_foo #=> false
|
515
161
|
```
|
516
162
|
|
517
|
-
|
518
|
-
|
519
|
-
These examples are based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions)
|
520
|
-
in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
|
521
|
-
|
522
|
-
Erlang:
|
523
|
-
|
524
|
-
```erlang
|
525
|
-
old_enough(X) when X >= 16 -> true;
|
526
|
-
old_enough(_) -> false.
|
527
|
-
|
528
|
-
right_age(X) when X >= 16, X =< 104 ->
|
529
|
-
true;
|
530
|
-
right_age(_) ->
|
531
|
-
false.
|
532
|
-
|
533
|
-
wrong_age(X) when X < 16; X > 104 ->
|
534
|
-
true;
|
535
|
-
wrong_age(_) ->
|
536
|
-
false.
|
537
|
-
```
|
163
|
+
### Goroutine (Go)
|
538
164
|
|
539
165
|
```ruby
|
540
|
-
|
541
|
-
defn(:old_enough, _){ false }
|
542
|
-
|
543
|
-
defn(:right_age, _) {
|
544
|
-
true
|
545
|
-
}.when{|x| x >= 16 && x <= 104 }
|
546
|
-
|
547
|
-
defn(:right_age, _) {
|
548
|
-
false
|
549
|
-
}
|
550
|
-
|
551
|
-
defn(:wrong_age, _) {
|
552
|
-
false
|
553
|
-
}.when{|x| x < 16 || x > 104 }
|
166
|
+
require 'functional/concurrency'
|
554
167
|
|
555
|
-
|
556
|
-
|
557
|
-
|
168
|
+
@expected = nil
|
169
|
+
go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
|
170
|
+
sleep(0.1)
|
171
|
+
@expected #=> [3, 2, 1]
|
558
172
|
```
|
559
173
|
|
560
|
-
|
561
|
-
|
562
|
-
The `behavior` functionality is not imported by default. It requires a separate `require` statement:
|
174
|
+
### Agent (Clojure)
|
563
175
|
|
564
176
|
```ruby
|
565
|
-
require '
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
require 'behaviour'
|
570
|
-
```
|
177
|
+
require 'functional/agent'
|
178
|
+
# or
|
179
|
+
require 'functional/concurrency'
|
571
180
|
|
572
|
-
|
573
|
-
|
574
|
-
`behavior_info` (or `behaviour_info`) is a symbol name for the behavior. The remaining parameter
|
575
|
-
is a hash of function names and their arity:
|
181
|
+
score = agent(10)
|
182
|
+
score.value #=> 10
|
576
183
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
# -or (for the Java/C# crowd)
|
184
|
+
score << proc{|current| current + 100 }
|
185
|
+
sleep(0.1)
|
186
|
+
score.value #=> 110
|
581
187
|
|
582
|
-
|
188
|
+
score << proc{|current| current * 2 }
|
189
|
+
sleep(0.1)
|
190
|
+
deref score #=> 220
|
583
191
|
|
192
|
+
score << proc{|current| current - 50 }
|
193
|
+
sleep(0.1)
|
194
|
+
score.value #=> 170
|
584
195
|
```
|
585
196
|
|
586
|
-
|
587
|
-
[Method#arity](http://ruby-doc.org/core-1.9.3/Method.html#method-i-arity) function.
|
588
|
-
Though not explicitly documented, block arguments do not count toward a method's arity.
|
589
|
-
methods defined using this gem's `defn` function will always have an arity of -1,
|
590
|
-
regardless of how many overloads are defined.
|
591
|
-
|
592
|
-
To enforce a behavior on a class simply call the `behavior` function within the class,
|
593
|
-
passing the name of the desired behavior:
|
197
|
+
### Future (Clojure)
|
594
198
|
|
595
199
|
```ruby
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
end
|
600
|
-
|
601
|
-
# or use the idiomatic Erlang spelling
|
602
|
-
class Bar
|
603
|
-
behaviour(:gen_foo)
|
604
|
-
...
|
605
|
-
end
|
200
|
+
require 'functional/future'
|
201
|
+
# or
|
202
|
+
require 'functional/concurrency'
|
606
203
|
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
204
|
+
count = future{ sleep(1); 10 }
|
205
|
+
count.state #=> :pending
|
206
|
+
# do stuff...
|
207
|
+
count.value #=> 10 (after blocking)
|
208
|
+
deref count #=> 10
|
612
209
|
```
|
613
210
|
|
614
|
-
|
615
|
-
raise an exception when you try to create an object from the class:
|
211
|
+
### Promise (JavaScript)
|
616
212
|
|
617
|
-
|
618
|
-
|
619
|
-
```
|
620
|
-
|
621
|
-
As an added bonus, Ruby [Object](http://ruby-doc.org/core-1.9.3/Object.html) will be
|
622
|
-
monkey-patched with a `behaves_as?` predicate method.
|
623
|
-
|
624
|
-
A complete example:
|
213
|
+
* [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)
|
214
|
+
* [Promises/A+](http://promises-aplus.github.io/promises-spec/)
|
625
215
|
|
626
216
|
```ruby
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
behavior(:gen_foo)
|
631
|
-
|
632
|
-
def foo
|
633
|
-
return 'foo/0'
|
634
|
-
end
|
635
|
-
|
636
|
-
def bar(one, &block)
|
637
|
-
return 'bar/1'
|
638
|
-
end
|
639
|
-
|
640
|
-
def baz(one, two)
|
641
|
-
return 'baz/2'
|
642
|
-
end
|
217
|
+
require 'functional/promise'
|
218
|
+
# or
|
219
|
+
require 'functional/concurrency'
|
643
220
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
end
|
651
|
-
end
|
652
|
-
|
653
|
-
foo = Foo.new
|
654
|
-
|
655
|
-
foo.behaves_as? :gen_foo #=> true
|
656
|
-
foo.behaves_as?(:bogus) #=> false
|
657
|
-
'foo'.behaves_as? :gen_foo #=> false
|
221
|
+
p = promise("Jerry", "D'Antonio"){|a, b| "#{a} #{b}" }.
|
222
|
+
then{|result| "Hello #{result}." }.
|
223
|
+
rescue(StandardError){|ex| puts "Boom!" }.
|
224
|
+
then{|result| "#{result} Would you like to play a game?"}
|
225
|
+
sleep(1)
|
226
|
+
p.value #=> "Hello Jerry D'Antonio. Would you like to play a game?"
|
658
227
|
```
|
659
228
|
|
660
|
-
|
661
|
-
|
662
|
-
Convenience functions are not imported by default. It require a separate `require` statement:
|
229
|
+
### Thread Pools
|
663
230
|
|
664
231
|
```ruby
|
665
|
-
require 'functional/
|
666
|
-
|
232
|
+
require 'functional/fixed_thread_pool'
|
233
|
+
require 'functional/cached_thread_pool'
|
234
|
+
# or
|
235
|
+
require 'functional/concurrency'
|
236
|
+
|
237
|
+
pool = Functional::FixedThreadPool.new(10)
|
238
|
+
@expected = 0
|
239
|
+
pool.post{ sleep(0.5); @expected += 100 }
|
240
|
+
pool.post{ sleep(0.5); @expected += 100 }
|
241
|
+
pool.post{ sleep(0.5); @expected += 100 }
|
242
|
+
@expected #=> nil
|
243
|
+
sleep(1)
|
244
|
+
@expected #=> 300
|
245
|
+
|
246
|
+
pool = Functional::CachedThreadPool.new
|
247
|
+
@expected = 0
|
248
|
+
pool << proc{ sleep(0.5); @expected += 10 }
|
249
|
+
pool << proc{ sleep(0.5); @expected += 10 }
|
250
|
+
pool << proc{ sleep(0.5); @expected += 10 }
|
251
|
+
@expected #=> 0
|
252
|
+
sleep(1)
|
253
|
+
@expected #=> 30
|
254
|
+
```
|
255
|
+
|
256
|
+
### Utilities
|
257
|
+
|
258
|
+
Documentation: [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
|
667
259
|
|
668
260
|
```ruby
|
669
261
|
Infinity #=> Infinity
|
@@ -678,9 +270,9 @@ pp_s [1,2,3,4] #=> "[1, 2, 3, 4]\n" props to Rha7
|
|
678
270
|
|
679
271
|
delta(-1, 1) #=> 2
|
680
272
|
delta({count: -1}, {count: 1}){|item| item[:count]} #=> 2
|
681
|
-
```
|
682
273
|
|
683
|
-
|
274
|
+
# And many more!
|
275
|
+
```
|
684
276
|
|
685
277
|
## Copyright
|
686
278
|
|