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
data/md/promise.md ADDED
@@ -0,0 +1,220 @@
1
+ # Promises, promises...
2
+
3
+ > A promise represents the eventual value returned from the single completion of an operation.
4
+
5
+ Promises have become an extremely important async technique in JavaScript. A promise is an
6
+ an operation that is performed asynchronously and is guaranteed to either succeed and return
7
+ a value or fail with a reason. What makes promises distinctly different from futures is
8
+ that promises can be chained such that the result of onw promise is passed to zero or
9
+ more children. Order of execution is guaranteed based on the order the promises are
10
+ created and parent promises are guaranteed to be complete before their children. Once a
11
+ promise has been fulfilled or rejected the corresponding value/reason can be retrieved.
12
+
13
+ ## The shoulders of giants
14
+
15
+ Inspiration for this implementation came from the CommonJS
16
+ [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) proposal and the
17
+ [Promises/A+](http://promises-aplus.github.io/promises-spec/) specification.
18
+ This implementation is specifically tailored to the idioms and practices of
19
+ Ruby. It is not 100% compliant with either of the aforementioned specifications.
20
+
21
+ ## Usage
22
+
23
+
24
+ Start by requiring promises
25
+
26
+ ```ruby
27
+ require 'functional/promise'
28
+ ```
29
+
30
+ Then create one
31
+
32
+ ```ruby
33
+ p = Promise.new("Jerry", "D'Antonio") do |first, last|
34
+ "#{last}, #{first}"
35
+ end
36
+
37
+ # -or-
38
+
39
+ p = promise(10){|x| x * x * x }
40
+ ```
41
+
42
+ Promises can be chained using the `then` method. The `then` method
43
+ accepts a block but no arguments. The result of the each promise is
44
+ passed as the block argument to chained promises
45
+
46
+
47
+ ```ruby
48
+ p = promise(10){|x| x * 2}.then{|result| result - 10 }
49
+ ```
50
+
51
+ And so on, and so on, and so on...
52
+
53
+ ```ruby
54
+ p = promise(10){|x| x * 2}.
55
+ then{|result| result - 10 }.
56
+ then{|result| result * 3 }.
57
+ then{|result| result % 5 }
58
+ ```
59
+
60
+ Promises are executed asynchronously so a newly-created promise
61
+ *should* always be in the pending state
62
+
63
+
64
+ ```ruby
65
+ p = promise{ "Hello, world!" }
66
+ p.state #=> :pending
67
+ p.pending? #=> true
68
+ ```
69
+
70
+ Wait a little bit, and the promise will resolve and provide a value
71
+
72
+
73
+ ```ruby
74
+ p = promise{ "Hello, world!" }
75
+ sleep(0.1)
76
+
77
+ p.state #=> :fulfilled
78
+ p.fulfilled? #=> true
79
+
80
+ p.value #=> "Hello, world!"
81
+
82
+ ```
83
+
84
+ If an exception occurs, the promise will be rejected and will provide
85
+ a reason for the rejection
86
+
87
+
88
+ ```ruby
89
+ p = promise{ raise StandardError.new("Here comes the Boom!") }
90
+ sleep(0.1)
91
+
92
+ p.state #=> :rejected
93
+ p.rejected? #=> true
94
+
95
+ p.reason=> #=> "#<StandardError: Here comes the Boom!>"
96
+ ```
97
+
98
+ ### Rejection
99
+
100
+ Much like the economy, rejection exhibits a trickle-down effect. When
101
+ a promise is rejected all its children will be rejected
102
+
103
+ ```ruby
104
+ p = [ promise{ Thread.pass; raise StandardError } ]
105
+
106
+ 10.times{|i| p << p.first.then{ i } }
107
+ sleep(0.1)
108
+
109
+ p.length #=> 11
110
+ p.first.state #=> :rejected
111
+ p.last.state #=> :rejected
112
+ ```
113
+
114
+ Once a promise is rejected it will not accept any children. Calls
115
+ to `then` will continually return `self`
116
+
117
+ ```ruby
118
+ p = promise{ raise StandardError }
119
+ sleep(0.1)
120
+
121
+ p.object_id #=> 32960556
122
+ p.then{}.object_id #=> 32960556
123
+ p.then{}.object_id #=> 32960556
124
+ ```
125
+
126
+ ### Error Handling
127
+
128
+ Promises support error handling callbacks is a style mimicing Ruby's
129
+ own exception handling mechanism, namely `rescue`
130
+
131
+
132
+ ```ruby
133
+ promise{ "dangerous operation..." }.rescue{|ex| puts "Bam!" }
134
+
135
+ # -or- (for the Java/C# crowd)
136
+ promise{ "dangerous operation..." }.catch{|ex| puts "Boom!" }
137
+
138
+ # -or- (for the hipsters)
139
+ promise{ "dangerous operation..." }.on_error{|ex| puts "Pow!" }
140
+ ```
141
+
142
+ As with Ruby's `rescue` mechanism, a promise's `rescue` method can
143
+ accept an optional Exception class argument (defaults to `Exception`
144
+ when not specified)
145
+
146
+
147
+ ```ruby
148
+ promise{ "dangerous operation..." }.rescue(ArgumentError){|ex| puts "Bam!" }
149
+ ```
150
+
151
+ Calls to `rescue` can also be chained
152
+
153
+ ```ruby
154
+ promise{ "dangerous operation..." }.
155
+ rescue(ArgumentError){|ex| puts "Bam!" }.
156
+ rescue(NoMethodError){|ex| puts "Boom!" }.
157
+ rescue(StandardError){|ex| puts "Pow!" }
158
+ ```
159
+
160
+ When there are multiple `rescue` handlers the first one to match the thrown
161
+ exception will be triggered
162
+
163
+ ```ruby
164
+ promise{ raise NoMethodError }.
165
+ rescue(ArgumentError){|ex| puts "Bam!" }.
166
+ rescue(NoMethodError){|ex| puts "Boom!" }.
167
+ rescue(StandardError){|ex| puts "Pow!" }
168
+
169
+ sleep(0.1)
170
+
171
+ #=> Boom!
172
+ ```
173
+
174
+ Trickle-down rejection also applies to rescue handlers. When a promise is rejected,
175
+ for any reason, its rescue handlers will be triggered. Rejection of the parent counts.
176
+
177
+ ```ruby
178
+ promise{ Thread.pass; raise StandardError }.
179
+ then{ true }.rescue{ puts 'Boom!' }.
180
+ then{ true }.rescue{ puts 'Boom!' }.
181
+ then{ true }.rescue{ puts 'Boom!' }.
182
+ then{ true }.rescue{ puts 'Boom!' }.
183
+ then{ true }.rescue{ puts 'Boom!' }
184
+ sleep(0.1)
185
+
186
+ #=> Boom!
187
+ #=> Boom!
188
+ #=> Boom!
189
+ #=> Boom!
190
+ #=> Boom!
191
+ ```
192
+
193
+ ## Copyright
194
+
195
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
196
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
197
+
198
+ ## License
199
+
200
+ Released under the MIT license.
201
+
202
+ http://www.opensource.org/licenses/mit-license.php
203
+
204
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
205
+ > of this software and associated documentation files (the "Software"), to deal
206
+ > in the Software without restriction, including without limitation the rights
207
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
208
+ > copies of the Software, and to permit persons to whom the Software is
209
+ > furnished to do so, subject to the following conditions:
210
+ >
211
+ > The above copyright notice and this permission notice shall be included in
212
+ > all copies or substantial portions of the Software.
213
+ >
214
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
215
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
216
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
217
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
218
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
219
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
220
+ > THE SOFTWARE.
data/md/utilities.md ADDED
@@ -0,0 +1,53 @@
1
+ # Utility Functions
2
+
3
+ Convenience functions are not imported by default. They need a separate `require` statement:
4
+
5
+ ```ruby
6
+ require 'functional/utilities'
7
+ ```
8
+
9
+ This gives you access to a few constants and functions:
10
+
11
+ ```ruby
12
+ Infinity #=> Infinity
13
+ NaN #=> NaN
14
+
15
+ repl? #=> true when called under irb, pry, bundle console, or rails console
16
+
17
+ safe(1, 2){|a, b| a + b} #=> 3
18
+ safe{ eval 'puts "Hello World!"' } #=> SecurityError: Insecure operation
19
+
20
+ pp_s [1,2,3,4] #=> "[1, 2, 3, 4]\n" props to Rha7
21
+
22
+ delta(-1, 1) #=> 2
23
+ delta({count: -1}, {count: 1}){|item| item[:count]} #=> 2
24
+ ```
25
+
26
+ ## Copyright
27
+
28
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
29
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
30
+
31
+ ## License
32
+
33
+ Released under the MIT license.
34
+
35
+ http://www.opensource.org/licenses/mit-license.php
36
+
37
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
38
+ > of this software and associated documentation files (the "Software"), to deal
39
+ > in the Software without restriction, including without limitation the rights
40
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41
+ > copies of the Software, and to permit persons to whom the Software is
42
+ > furnished to do so, subject to the following conditions:
43
+ >
44
+ > The above copyright notice and this permission notice shall be included in
45
+ > all copies or substantial portions of the Software.
46
+ >
47
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
50
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
51
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
52
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
53
+ > THE SOFTWARE.
@@ -0,0 +1,405 @@
1
+ require 'spec_helper'
2
+
3
+ module Functional
4
+
5
+ describe Agent do
6
+
7
+ subject { Agent.new(0) }
8
+
9
+ let(:observer) do
10
+ Class.new do
11
+ attr_reader :value
12
+ define_method(:update) do |time, value|
13
+ @value = value
14
+ end
15
+ end.new
16
+ end
17
+
18
+ before(:each) do
19
+ $GLOBAL_THREAD_POOL = CachedThreadPool.new
20
+ end
21
+
22
+ context '#initialize' do
23
+
24
+ it 'sets the value to the given initial state' do
25
+ Agent.new(10).value.should eq 10
26
+ end
27
+
28
+ it 'sets the timeout to the given value' do
29
+ Agent.new(0, 5).timeout.should eq 5
30
+ end
31
+
32
+ it 'sets the timeout to the default when nil' do
33
+ Agent.new(0).timeout.should eq Agent::TIMEOUT
34
+ end
35
+
36
+ it 'sets the length to zero' do
37
+ Agent.new(10).length.should eq 0
38
+ end
39
+
40
+ it 'spawns the worker thread' do
41
+ $GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
42
+ Agent.new(0)
43
+ end
44
+ end
45
+
46
+ context '#rescue' do
47
+
48
+ it 'returns self when a block is given' do
49
+ a1 = subject
50
+ a2 = a1.rescue{}
51
+ a1.object_id.should eq a2.object_id
52
+ end
53
+
54
+ it 'returns self when no block is given' do
55
+ a1 = subject
56
+ a2 = a1.rescue
57
+ a1.object_id.should eq a2.object_id
58
+ end
59
+
60
+ it 'accepts an exception class as the first parameter' do
61
+ lambda {
62
+ subject.rescue(StandardError){}
63
+ }.should_not raise_error(ArgumentError)
64
+ end
65
+
66
+ it 'ignores rescuers without a block' do
67
+ subject.rescue
68
+ subject.instance_variable_get(:@rescuers).should be_empty
69
+ end
70
+ end
71
+
72
+ context '#validate' do
73
+
74
+ it 'returns self when a block is given' do
75
+ a1 = subject
76
+ a2 = a1.validate{}
77
+ a1.object_id.should eq a2.object_id
78
+ end
79
+
80
+ it 'returns self when no block is given' do
81
+ a1 = subject
82
+ a2 = a1.validate
83
+ a1.object_id.should eq a2.object_id
84
+ end
85
+
86
+ it 'ignores validators without a block' do
87
+ subject.validate
88
+ subject.instance_variable_get(:@validator).should be_nil
89
+ end
90
+ end
91
+
92
+ context '#post' do
93
+
94
+ it 'adds the given block to the queue' do
95
+ before = subject.length
96
+ subject.post{ nil }
97
+ subject.post{ nil }
98
+ subject.length.should eq before+2
99
+ end
100
+
101
+ it 'does not add to the queue when no block is given' do
102
+ before = subject.length
103
+ subject.post
104
+ subject.post{ nil }
105
+ subject.length.should eq before+1
106
+ end
107
+ end
108
+
109
+ context '#length' do
110
+
111
+ it 'should be zero for a new agent' do
112
+ subject.length.should eq 0
113
+ end
114
+
115
+ it 'should increase by one for each #post' do
116
+ subject.post{ sleep }
117
+ subject.post{ sleep }
118
+ subject.post{ sleep }
119
+ subject.length.should eq 3
120
+ end
121
+
122
+ it 'should decrease by one each time a handler is run' do
123
+ subject.post{ nil }
124
+ subject.post{ sleep }
125
+ subject.post{ sleep }
126
+ sleep(0.1)
127
+ subject.length.should eq 1
128
+ end
129
+ end
130
+
131
+ context 'fulfillment' do
132
+
133
+ it 'process each block in the queue' do
134
+ @expected = []
135
+ subject.post{ @expected << 1 }
136
+ subject.post{ @expected << 2 }
137
+ subject.post{ @expected << 3 }
138
+ sleep(0.1)
139
+ @expected.should eq [1,2,3]
140
+ end
141
+
142
+ it 'passes the current value to the handler' do
143
+ @expected = nil
144
+ Agent.new(10).post{|i| @expected = i }
145
+ sleep(0.1)
146
+ @expected.should eq 10
147
+ end
148
+
149
+ it 'sets the value to the handler return value on success' do
150
+ subject.post{ 100 }
151
+ sleep(0.1)
152
+ subject.value.should eq 100
153
+ end
154
+
155
+ it 'rejects the handler after timeout reached' do
156
+ agent = Agent.new(0, 0.1)
157
+ agent.post{ sleep(1); 10 }
158
+ agent.value.should eq 0
159
+ end
160
+ end
161
+
162
+ context 'validation' do
163
+
164
+ it 'processes the validator when present' do
165
+ @expected = nil
166
+ subject.validate{ @expected = 10; true }
167
+ subject.post{ nil }
168
+ sleep(0.1)
169
+ @expected.should eq 10
170
+ end
171
+
172
+ it 'passes the new value to the validator' do
173
+ @expected = nil
174
+ subject.validate{|v| @expected = v; true }
175
+ subject.post{ 10 }
176
+ sleep(0.1)
177
+ @expected.should eq 10
178
+ end
179
+
180
+ it 'sets the new value when the validator returns true' do
181
+ agent = Agent.new(0).validate{ true }
182
+ agent.post{ 10 }
183
+ sleep(0.1)
184
+ agent.value.should eq 10
185
+ end
186
+
187
+ it 'does not change the value when the validator returns false' do
188
+ agent = Agent.new(0).validate{ false }
189
+ agent.post{ 10 }
190
+ sleep(0.1)
191
+ agent.value.should eq 0
192
+ end
193
+
194
+ it 'does not change the value when the validator raises an exception' do
195
+ agent = Agent.new(0).validate{ raise StandardError }
196
+ agent.post{ 10 }
197
+ sleep(0.1)
198
+ agent.value.should eq 0
199
+ end
200
+ end
201
+
202
+ context 'rejection' do
203
+
204
+ it 'calls the first exception block with a matching class' do
205
+ @expected = nil
206
+ subject.
207
+ rescue(StandardError){|ex| @expected = 1 }.
208
+ rescue(StandardError){|ex| @expected = 2 }.
209
+ rescue(StandardError){|ex| @expected = 3 }
210
+ subject.post{ raise StandardError }
211
+ sleep(0.1)
212
+ @expected.should eq 1
213
+ end
214
+
215
+ it 'matches all with a rescue with no class given' do
216
+ @expected = nil
217
+ subject.
218
+ rescue(LoadError){|ex| @expected = 1 }.
219
+ rescue{|ex| @expected = 2 }.
220
+ rescue(StandardError){|ex| @expected = 3 }
221
+ subject.post{ raise NoMethodError }
222
+ sleep(0.1)
223
+ @expected.should eq 2
224
+ end
225
+
226
+ it 'searches associated rescue handlers in order' do
227
+ @expected = nil
228
+ subject.
229
+ rescue(ArgumentError){|ex| @expected = 1 }.
230
+ rescue(LoadError){|ex| @expected = 2 }.
231
+ rescue(Exception){|ex| @expected = 3 }
232
+ subject.post{ raise ArgumentError }
233
+ sleep(0.1)
234
+ @expected.should eq 1
235
+
236
+ @expected = nil
237
+ subject.
238
+ rescue(ArgumentError){|ex| @expected = 1 }.
239
+ rescue(LoadError){|ex| @expected = 2 }.
240
+ rescue(Exception){|ex| @expected = 3 }
241
+ subject.post{ raise LoadError }
242
+ sleep(0.1)
243
+ @expected.should eq 2
244
+
245
+ @expected = nil
246
+ subject.
247
+ rescue(ArgumentError){|ex| @expected = 1 }.
248
+ rescue(LoadError){|ex| @expected = 2 }.
249
+ rescue(Exception){|ex| @expected = 3 }
250
+ subject.post{ raise StandardError }
251
+ sleep(0.1)
252
+ @expected.should eq 3
253
+ end
254
+
255
+ it 'passes the exception object to the matched block' do
256
+ @expected = nil
257
+ subject.
258
+ rescue(ArgumentError){|ex| @expected = ex }.
259
+ rescue(LoadError){|ex| @expected = ex }.
260
+ rescue(Exception){|ex| @expected = ex }
261
+ subject.post{ raise StandardError }
262
+ sleep(0.1)
263
+ @expected.should be_a(StandardError)
264
+ end
265
+
266
+ it 'ignores rescuers without a block' do
267
+ @expected = nil
268
+ subject.
269
+ rescue(StandardError).
270
+ rescue(StandardError){|ex| @expected = ex }.
271
+ rescue(Exception){|ex| @expected = ex }
272
+ subject.post{ raise StandardError }
273
+ sleep(0.1)
274
+ @expected.should be_a(StandardError)
275
+ end
276
+
277
+ it 'supresses the exception if no rescue matches' do
278
+ lambda {
279
+ subject.
280
+ rescue(ArgumentError){|ex| @expected = ex }.
281
+ rescue(StandardError){|ex| @expected = ex }.
282
+ rescue(Exception){|ex| @expected = ex }
283
+ subject.post{ raise StandardError }
284
+ sleep(0.1)
285
+ }.should_not raise_error
286
+ end
287
+
288
+ it 'supresses exceptions thrown from rescue handlers' do
289
+ lambda {
290
+ subject.rescue(Exception){ raise StandardError }
291
+ subject.post{ raise ArgumentError }
292
+ sleep(0.1)
293
+ }.should_not raise_error(StandardError)
294
+ end
295
+ end
296
+
297
+ context 'observation' do
298
+
299
+ it 'notifies all observers when the value changes' do
300
+ agent = Agent.new(0)
301
+ agent.add_observer(observer)
302
+ agent.post{ 10 }
303
+ sleep(0.1)
304
+ observer.value.should eq 10
305
+ end
306
+
307
+ it 'does not notify observers when validation fails' do
308
+ agent = Agent.new(0)
309
+ agent.validate{ false }
310
+ agent.add_observer(observer)
311
+ agent.post{ 10 }
312
+ sleep(0.1)
313
+ observer.value.should be_nil
314
+ end
315
+
316
+ it 'does not notify observers when the handler raises an exception' do
317
+ agent = Agent.new(0)
318
+ agent.add_observer(observer)
319
+ agent.post{ raise StandardError }
320
+ sleep(0.1)
321
+ observer.value.should be_nil
322
+ end
323
+ end
324
+
325
+ context 'aliases' do
326
+
327
+ it 'aliases #deref for #value' do
328
+ Agent.new(10).deref.should eq 10
329
+ end
330
+
331
+ it 'aliases #validates for :validate' do
332
+ @expected = nil
333
+ subject.validates{|v| @expected = v }
334
+ subject.post{ 10 }
335
+ sleep(0.1)
336
+ @expected.should eq 10
337
+ end
338
+
339
+ it 'aliases #validate_with for :validate' do
340
+ @expected = nil
341
+ subject.validate_with{|v| @expected = v }
342
+ subject.post{ 10 }
343
+ sleep(0.1)
344
+ @expected.should eq 10
345
+ end
346
+
347
+ it 'aliases #validates_with for :validate' do
348
+ @expected = nil
349
+ subject.validates_with{|v| @expected = v }
350
+ subject.post{ 10 }
351
+ sleep(0.1)
352
+ @expected.should eq 10
353
+ end
354
+
355
+ it 'aliases #catch for #rescue' do
356
+ @expected = nil
357
+ subject.catch{ @expected = true }
358
+ subject.post{ raise StandardError }
359
+ sleep(0.1)
360
+ @expected.should be_true
361
+ end
362
+
363
+ it 'aliases #on_error for #rescue' do
364
+ @expected = nil
365
+ subject.on_error{ @expected = true }
366
+ subject.post{ raise StandardError }
367
+ sleep(0.1)
368
+ @expected.should be_true
369
+ end
370
+
371
+ it 'aliases #add_watch for #add_observer' do
372
+ agent = Agent.new(0)
373
+ agent.add_watch(observer)
374
+ agent.post{ 10 }
375
+ sleep(0.1)
376
+ observer.value.should eq 10
377
+ end
378
+
379
+ it 'aliases #<< for Agent#post' do
380
+ subject << proc{ 100 }
381
+ sleep(0.1)
382
+ subject.value.should eq 100
383
+
384
+ subject << lambda{ 100 }
385
+ sleep(0.1)
386
+ subject.value.should eq 100
387
+ end
388
+
389
+ it 'aliases Kernel#agent for Agent.new' do
390
+ agent(10).should be_a(Agent)
391
+ end
392
+
393
+ it 'aliases Kernel#deref for #deref' do
394
+ deref(Agent.new(10)).should eq 10
395
+ deref(Agent.new(10), 10).should eq 10
396
+ end
397
+
398
+ it 'aliases Kernel:post for Agent#post' do
399
+ post(subject){ 100 }
400
+ sleep(0.1)
401
+ subject.value.should eq 100
402
+ end
403
+ end
404
+ end
405
+ end