functional-ruby 0.6.0 → 0.7.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 +14 -126
- data/lib/functional.rb +4 -1
- data/lib/functional/utilities.rb +46 -0
- data/lib/functional/version.rb +1 -1
- data/lib/functional_ruby.rb +1 -1
- data/md/utilities.md +2 -0
- data/spec/functional/behavior_spec.rb +2 -2
- data/spec/functional/pattern_matching_spec.rb +2 -2
- data/spec/functional/utilities_spec.rb +131 -43
- data/spec/spec_helper.rb +1 -3
- metadata +3 -40
- data/lib/functional/agent.rb +0 -130
- data/lib/functional/all.rb +0 -13
- data/lib/functional/cached_thread_pool.rb +0 -122
- data/lib/functional/concurrency.rb +0 -35
- data/lib/functional/core.rb +0 -2
- data/lib/functional/event.rb +0 -53
- data/lib/functional/event_machine_defer_proxy.rb +0 -23
- data/lib/functional/fixed_thread_pool.rb +0 -89
- data/lib/functional/future.rb +0 -42
- data/lib/functional/global_thread_pool.rb +0 -3
- data/lib/functional/obligation.rb +0 -121
- data/lib/functional/promise.rb +0 -194
- data/lib/functional/thread_pool.rb +0 -61
- data/md/concurrency.md +0 -465
- data/md/future.md +0 -32
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -220
- data/spec/functional/agent_spec.rb +0 -405
- data/spec/functional/cached_thread_pool_spec.rb +0 -112
- data/spec/functional/concurrency_spec.rb +0 -55
- data/spec/functional/event_machine_defer_proxy_spec.rb +0 -246
- data/spec/functional/event_spec.rb +0 -114
- data/spec/functional/fixed_thread_pool_spec.rb +0 -84
- data/spec/functional/future_spec.rb +0 -115
- data/spec/functional/obligation_shared.rb +0 -121
- data/spec/functional/promise_spec.rb +0 -310
- data/spec/functional/thread_pool_shared.rb +0 -209
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'functional/global_thread_pool'
|
2
|
-
|
3
|
-
module Functional
|
4
|
-
|
5
|
-
class EventMachineDeferProxy
|
6
|
-
behavior(:global_thread_pool)
|
7
|
-
|
8
|
-
def post(*args, &block)
|
9
|
-
if args.empty?
|
10
|
-
EventMachine.defer(block)
|
11
|
-
else
|
12
|
-
new_block = proc{ block.call(*args) }
|
13
|
-
EventMachine.defer(new_block)
|
14
|
-
end
|
15
|
-
return true
|
16
|
-
end
|
17
|
-
|
18
|
-
def <<(block)
|
19
|
-
EventMachine.defer(block)
|
20
|
-
return self
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,89 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'functional/thread_pool'
|
4
|
-
require 'functional/event'
|
5
|
-
|
6
|
-
module Functional
|
7
|
-
|
8
|
-
def self.new_fixed_thread_pool(size)
|
9
|
-
return FixedThreadPool.new(size)
|
10
|
-
end
|
11
|
-
|
12
|
-
class FixedThreadPool < ThreadPool
|
13
|
-
behavior(:thread_pool)
|
14
|
-
|
15
|
-
MIN_POOL_SIZE = 1
|
16
|
-
MAX_POOL_SIZE = 1024
|
17
|
-
|
18
|
-
def initialize(size)
|
19
|
-
super()
|
20
|
-
if size < MIN_POOL_SIZE || size > MAX_POOL_SIZE
|
21
|
-
raise ArgumentError.new("size must be between #{MIN_POOL_SIZE} and #{MAX_POOL_SIZE}")
|
22
|
-
end
|
23
|
-
|
24
|
-
@pool = size.times.collect{ create_worker_thread }
|
25
|
-
collect_garbage
|
26
|
-
end
|
27
|
-
|
28
|
-
def kill
|
29
|
-
@status = :killed
|
30
|
-
@pool.each{|t| Thread.kill(t) }
|
31
|
-
end
|
32
|
-
|
33
|
-
def size
|
34
|
-
if running?
|
35
|
-
return @pool.length
|
36
|
-
else
|
37
|
-
return 0
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def post(*args, &block)
|
42
|
-
raise ArgumentError.new('no block given') unless block_given?
|
43
|
-
if running?
|
44
|
-
@queue << [args, block]
|
45
|
-
return true
|
46
|
-
else
|
47
|
-
return false
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# @private
|
52
|
-
def status # :nodoc:
|
53
|
-
@pool.collect{|t| t.status }
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
# @private
|
59
|
-
def create_worker_thread # :nodoc:
|
60
|
-
Thread.new do
|
61
|
-
loop do
|
62
|
-
task = @queue.pop
|
63
|
-
if task == :stop
|
64
|
-
break
|
65
|
-
else
|
66
|
-
task.last.call(*task.first)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
@pool.delete(Thread.current)
|
70
|
-
if @pool.empty?
|
71
|
-
@termination.set
|
72
|
-
@status = :shutdown unless killed?
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# @private
|
78
|
-
def collect_garbage # :nodoc:
|
79
|
-
@collector = Thread.new do
|
80
|
-
sleep(1)
|
81
|
-
@pool.size.times do |i|
|
82
|
-
if @pool[i].status.nil?
|
83
|
-
@pool[i] = create_worker_thread
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
data/lib/functional/future.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'functional/obligation'
|
4
|
-
require 'functional/global_thread_pool'
|
5
|
-
|
6
|
-
module Functional
|
7
|
-
|
8
|
-
class Future
|
9
|
-
include Obligation
|
10
|
-
behavior(:future)
|
11
|
-
|
12
|
-
def initialize(*args)
|
13
|
-
|
14
|
-
unless block_given?
|
15
|
-
@state = :fulfilled
|
16
|
-
else
|
17
|
-
@value = nil
|
18
|
-
@state = :pending
|
19
|
-
$GLOBAL_THREAD_POOL.post do
|
20
|
-
semaphore.synchronize do
|
21
|
-
Thread.pass
|
22
|
-
begin
|
23
|
-
@value = yield(*args)
|
24
|
-
@state = :fulfilled
|
25
|
-
rescue Exception => ex
|
26
|
-
@state = :rejected
|
27
|
-
@reason = ex
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
module Kernel
|
37
|
-
|
38
|
-
def future(*args, &block)
|
39
|
-
return Functional::Future.new(*args, &block)
|
40
|
-
end
|
41
|
-
module_function :future
|
42
|
-
end
|
@@ -1,121 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
require 'timeout'
|
3
|
-
|
4
|
-
require 'functional/behavior'
|
5
|
-
|
6
|
-
behavior_info(:future,
|
7
|
-
state: 0,
|
8
|
-
value: -1,
|
9
|
-
reason: 0,
|
10
|
-
pending?: 0,
|
11
|
-
fulfilled?: 0,
|
12
|
-
rejected?: 0)
|
13
|
-
|
14
|
-
behavior_info(:promise,
|
15
|
-
state: 0,
|
16
|
-
value: -1,
|
17
|
-
reason: 0,
|
18
|
-
pending?: 0,
|
19
|
-
fulfilled?: 0,
|
20
|
-
rejected?: 0,
|
21
|
-
then: 0,
|
22
|
-
rescue: -1)
|
23
|
-
|
24
|
-
module Functional
|
25
|
-
|
26
|
-
module Obligation
|
27
|
-
|
28
|
-
attr_reader :state
|
29
|
-
attr_reader :reason
|
30
|
-
|
31
|
-
# Has the obligation been fulfilled?
|
32
|
-
# @return [Boolean]
|
33
|
-
def fulfilled?() return(@state == :fulfilled); end
|
34
|
-
alias_method :realized?, :fulfilled?
|
35
|
-
|
36
|
-
# Is obligation completion still pending?
|
37
|
-
# @return [Boolean]
|
38
|
-
def pending?() return(!(fulfilled? || rejected?)); end
|
39
|
-
|
40
|
-
def value(timeout = nil)
|
41
|
-
if !pending? || timeout == 0
|
42
|
-
return @value
|
43
|
-
elsif timeout.nil?
|
44
|
-
return semaphore.synchronize { @value }
|
45
|
-
else
|
46
|
-
begin
|
47
|
-
return Timeout::timeout(timeout.to_f) {
|
48
|
-
semaphore.synchronize { @value }
|
49
|
-
}
|
50
|
-
rescue Timeout::Error => ex
|
51
|
-
return nil
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
alias_method :deref, :value
|
56
|
-
|
57
|
-
# Has the promise been rejected?
|
58
|
-
# @return [Boolean]
|
59
|
-
def rejected?() return(@state == :rejected); end
|
60
|
-
|
61
|
-
protected
|
62
|
-
|
63
|
-
def semaphore
|
64
|
-
@semaphore ||= Mutex.new
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
module Kernel
|
70
|
-
|
71
|
-
def deref(obligation, timeout = nil)
|
72
|
-
if obligation.respond_to?(:deref)
|
73
|
-
return obligation.deref(timeout)
|
74
|
-
elsif obligation.respond_to?(:value)
|
75
|
-
return obligation.deref(timeout)
|
76
|
-
else
|
77
|
-
return nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
module_function :deref
|
81
|
-
|
82
|
-
def pending?(obligation)
|
83
|
-
if obligation.respond_to?(:pending?)
|
84
|
-
return obligation.pending?
|
85
|
-
else
|
86
|
-
return false
|
87
|
-
end
|
88
|
-
end
|
89
|
-
module_function :pending?
|
90
|
-
|
91
|
-
def fulfilled?(obligation)
|
92
|
-
if obligation.respond_to?(:fulfilled?)
|
93
|
-
return obligation.fulfilled?
|
94
|
-
elsif obligation.respond_to?(:realized?)
|
95
|
-
return obligation.realized?
|
96
|
-
else
|
97
|
-
return false
|
98
|
-
end
|
99
|
-
end
|
100
|
-
module_function :fulfilled?
|
101
|
-
|
102
|
-
def realized?(obligation)
|
103
|
-
if obligation.respond_to?(:realized?)
|
104
|
-
return obligation.realized?
|
105
|
-
elsif obligation.respond_to?(:fulfilled?)
|
106
|
-
return obligation.fulfilled?
|
107
|
-
else
|
108
|
-
return false
|
109
|
-
end
|
110
|
-
end
|
111
|
-
module_function :realized?
|
112
|
-
|
113
|
-
def rejected?(obligation)
|
114
|
-
if obligation.respond_to?(:rejected?)
|
115
|
-
return obligation.rejected?
|
116
|
-
else
|
117
|
-
return false
|
118
|
-
end
|
119
|
-
end
|
120
|
-
module_function :rejected?
|
121
|
-
end
|
data/lib/functional/promise.rb
DELETED
@@ -1,194 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'functional/obligation'
|
4
|
-
require 'functional/global_thread_pool'
|
5
|
-
|
6
|
-
module Functional
|
7
|
-
|
8
|
-
class Promise
|
9
|
-
include Obligation
|
10
|
-
behavior(:future)
|
11
|
-
behavior(:promise)
|
12
|
-
|
13
|
-
# Creates a new promise object. "A promise represents the eventual
|
14
|
-
# value returned from the single completion of an operation."
|
15
|
-
# Promises can be chained in a tree structure where each promise
|
16
|
-
# has zero or more children. Promises are resolved asynchronously
|
17
|
-
# in the order they are added to the tree. Parents are guaranteed
|
18
|
-
# to be resolved before their children. The result of each promise
|
19
|
-
# is passed to each of its children upon resolution. When
|
20
|
-
# a promise is rejected all its children will be summarily rejected.
|
21
|
-
# A promise that is neither resolved or rejected is pending.
|
22
|
-
#
|
23
|
-
# @param args [Array] zero or more arguments for the block
|
24
|
-
# @param block [Proc] the block to call when attempting fulfillment
|
25
|
-
#
|
26
|
-
# @see http://wiki.commonjs.org/wiki/Promises/A
|
27
|
-
# @see http://promises-aplus.github.io/promises-spec/
|
28
|
-
def initialize(*args, &block)
|
29
|
-
if args.first.is_a?(Promise)
|
30
|
-
@parent = args.first
|
31
|
-
else
|
32
|
-
@parent = nil
|
33
|
-
@chain = [self]
|
34
|
-
end
|
35
|
-
|
36
|
-
@mutex = Mutex.new
|
37
|
-
@handler = block || Proc.new{|result| result }
|
38
|
-
@state = :pending
|
39
|
-
@value = nil
|
40
|
-
@reason = nil
|
41
|
-
@children = []
|
42
|
-
@rescuers = []
|
43
|
-
|
44
|
-
realize(*args) if root?
|
45
|
-
end
|
46
|
-
|
47
|
-
# Create a new child Promise. The block argument for the child will
|
48
|
-
# be the result of fulfilling its parent. If the child will
|
49
|
-
# immediately be rejected if the parent has already been rejected.
|
50
|
-
#
|
51
|
-
# @param block [Proc] the block to call when attempting fulfillment
|
52
|
-
#
|
53
|
-
# @return [Promise] the new promise
|
54
|
-
def then(&block)
|
55
|
-
child = @mutex.synchronize do
|
56
|
-
block = Proc.new{|result| result } unless block_given?
|
57
|
-
@children << Promise.new(self, &block)
|
58
|
-
@children.last.on_reject(@reason) if rejected?
|
59
|
-
push(@children.last)
|
60
|
-
@children.last
|
61
|
-
end
|
62
|
-
return child
|
63
|
-
end
|
64
|
-
|
65
|
-
# Add a rescue handler to be run if the promise is rejected (via raised
|
66
|
-
# exception). Multiple rescue handlers may be added to a Promise.
|
67
|
-
# Rescue blocks will be checked in order and the first one with a
|
68
|
-
# matching Exception class will be processed. The block argument
|
69
|
-
# will be the exception that caused the rejection.
|
70
|
-
#
|
71
|
-
# @param clazz [Class] The class of exception to rescue
|
72
|
-
# @param block [Proc] the block to call if the rescue is matched
|
73
|
-
#
|
74
|
-
# @return [self] so that additional chaining can occur
|
75
|
-
def rescue(clazz = Exception, &block)
|
76
|
-
@mutex.synchronize do
|
77
|
-
@rescuers << Rescuer.new(clazz, block) if block_given?
|
78
|
-
end
|
79
|
-
return self
|
80
|
-
end
|
81
|
-
alias_method :catch, :rescue
|
82
|
-
alias_method :on_error, :rescue
|
83
|
-
|
84
|
-
protected
|
85
|
-
|
86
|
-
attr_reader :parent
|
87
|
-
attr_reader :handler
|
88
|
-
attr_reader :rescuers
|
89
|
-
|
90
|
-
# @private
|
91
|
-
Rescuer = Struct.new(:clazz, :block)
|
92
|
-
|
93
|
-
# @private
|
94
|
-
def root # :nodoc:
|
95
|
-
current = self
|
96
|
-
current = current.parent until current.root?
|
97
|
-
return current
|
98
|
-
end
|
99
|
-
|
100
|
-
# @private
|
101
|
-
def root? # :nodoc:
|
102
|
-
@parent.nil?
|
103
|
-
end
|
104
|
-
|
105
|
-
# @private
|
106
|
-
def push(promise) # :nodoc:
|
107
|
-
if root?
|
108
|
-
@chain << promise
|
109
|
-
else
|
110
|
-
@parent.push(promise)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# @private
|
115
|
-
def on_fulfill(value) # :nodoc:
|
116
|
-
@mutex.synchronize do
|
117
|
-
if pending?
|
118
|
-
@value = @handler.call(value)
|
119
|
-
@state = :fulfilled
|
120
|
-
@reason = nil
|
121
|
-
end
|
122
|
-
end
|
123
|
-
return @value
|
124
|
-
end
|
125
|
-
|
126
|
-
# @private
|
127
|
-
def on_reject(reason) # :nodoc:
|
128
|
-
@mutex.synchronize do
|
129
|
-
if pending?
|
130
|
-
@state = :rejected
|
131
|
-
@reason = reason
|
132
|
-
self.try_rescue(reason)
|
133
|
-
@value = nil
|
134
|
-
end
|
135
|
-
@children.each{|child| child.on_reject(reason) }
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
# @private
|
140
|
-
def try_rescue(ex) # :nodoc:
|
141
|
-
rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
|
142
|
-
rescuer.block.call(ex) if rescuer
|
143
|
-
rescue Exception => e
|
144
|
-
# supress
|
145
|
-
end
|
146
|
-
|
147
|
-
# @private
|
148
|
-
def realize(*args) # :nodoc:
|
149
|
-
$GLOBAL_THREAD_POOL.post(@chain, @mutex, args) do |chain, mutex, args|
|
150
|
-
result = args.length == 1 ? args.first : args
|
151
|
-
index = 0
|
152
|
-
loop do
|
153
|
-
Thread.pass
|
154
|
-
current = mutex.synchronize{ chain[index] }
|
155
|
-
unless current.rejected?
|
156
|
-
current.semaphore.synchronize do
|
157
|
-
begin
|
158
|
-
result = current.on_fulfill(result)
|
159
|
-
rescue Exception => ex
|
160
|
-
current.on_reject(ex)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
index += 1
|
165
|
-
sleep while index >= chain.length
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
module Kernel
|
173
|
-
|
174
|
-
# Creates a new promise object. "A promise represents the eventual
|
175
|
-
# value returned from the single completion of an operation."
|
176
|
-
# Promises can be chained in a tree structure where each promise
|
177
|
-
# has zero or more children. Promises are resolved asynchronously
|
178
|
-
# in the order they are added to the tree. Parents are guaranteed
|
179
|
-
# to be resolved before their children. The result of each promise
|
180
|
-
# is passes to each of its children when the child resolves. When
|
181
|
-
# a promise is rejected all its children will be summarily rejected.
|
182
|
-
# A promise added to a rejected promise will immediately be rejected.
|
183
|
-
# A promise that is neither resolved or rejected is pending.
|
184
|
-
#
|
185
|
-
# @param args [Array] zero or more arguments for the block
|
186
|
-
# @param block [Proc] the block to call when attempting fulfillment
|
187
|
-
#
|
188
|
-
# @see Promise
|
189
|
-
# @see http://wiki.commonjs.org/wiki/Promises/A
|
190
|
-
def promise(*args, &block)
|
191
|
-
return Functional::Promise.new(*args, &block)
|
192
|
-
end
|
193
|
-
module_function :promise
|
194
|
-
end
|