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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -126
  3. data/lib/functional.rb +4 -1
  4. data/lib/functional/utilities.rb +46 -0
  5. data/lib/functional/version.rb +1 -1
  6. data/lib/functional_ruby.rb +1 -1
  7. data/md/utilities.md +2 -0
  8. data/spec/functional/behavior_spec.rb +2 -2
  9. data/spec/functional/pattern_matching_spec.rb +2 -2
  10. data/spec/functional/utilities_spec.rb +131 -43
  11. data/spec/spec_helper.rb +1 -3
  12. metadata +3 -40
  13. data/lib/functional/agent.rb +0 -130
  14. data/lib/functional/all.rb +0 -13
  15. data/lib/functional/cached_thread_pool.rb +0 -122
  16. data/lib/functional/concurrency.rb +0 -35
  17. data/lib/functional/core.rb +0 -2
  18. data/lib/functional/event.rb +0 -53
  19. data/lib/functional/event_machine_defer_proxy.rb +0 -23
  20. data/lib/functional/fixed_thread_pool.rb +0 -89
  21. data/lib/functional/future.rb +0 -42
  22. data/lib/functional/global_thread_pool.rb +0 -3
  23. data/lib/functional/obligation.rb +0 -121
  24. data/lib/functional/promise.rb +0 -194
  25. data/lib/functional/thread_pool.rb +0 -61
  26. data/md/concurrency.md +0 -465
  27. data/md/future.md +0 -32
  28. data/md/obligation.md +0 -32
  29. data/md/promise.md +0 -220
  30. data/spec/functional/agent_spec.rb +0 -405
  31. data/spec/functional/cached_thread_pool_spec.rb +0 -112
  32. data/spec/functional/concurrency_spec.rb +0 -55
  33. data/spec/functional/event_machine_defer_proxy_spec.rb +0 -246
  34. data/spec/functional/event_spec.rb +0 -114
  35. data/spec/functional/fixed_thread_pool_spec.rb +0 -84
  36. data/spec/functional/future_spec.rb +0 -115
  37. data/spec/functional/obligation_shared.rb +0 -121
  38. data/spec/functional/promise_spec.rb +0 -310
  39. 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
@@ -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,3 +0,0 @@
1
- require 'functional/cached_thread_pool'
2
-
3
- $GLOBAL_THREAD_POOL ||= Functional::CachedThreadPool.new
@@ -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
@@ -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