concurrent-ruby 0.8.0 → 0.9.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +97 -2
- data/README.md +103 -54
- data/lib/concurrent.rb +34 -14
- data/lib/concurrent/async.rb +164 -50
- data/lib/concurrent/atom.rb +171 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
- data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
- data/lib/concurrent/atomic/atomic_reference.rb +49 -0
- data/lib/concurrent/atomic/condition.rb +23 -12
- data/lib/concurrent/atomic/count_down_latch.rb +23 -21
- data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
- data/lib/concurrent/atomic/event.rb +33 -42
- data/lib/concurrent/atomic/read_write_lock.rb +252 -0
- data/lib/concurrent/atomic/semaphore.rb +64 -89
- data/lib/concurrent/atomic/thread_local_var.rb +130 -58
- data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +3 -0
- data/lib/concurrent/atomic_reference/jruby.rb +6 -3
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +10 -32
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
- data/lib/concurrent/atomic_reference/rbx.rb +4 -1
- data/lib/concurrent/atomic_reference/ruby.rb +6 -3
- data/lib/concurrent/atomics.rb +74 -4
- data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
- data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
- data/lib/concurrent/collection/priority_queue.rb +300 -245
- data/lib/concurrent/concern/deprecation.rb +27 -0
- data/lib/concurrent/concern/dereferenceable.rb +88 -0
- data/lib/concurrent/concern/logging.rb +25 -0
- data/lib/concurrent/concern/obligation.rb +228 -0
- data/lib/concurrent/concern/observable.rb +85 -0
- data/lib/concurrent/configuration.rb +226 -112
- data/lib/concurrent/dataflow.rb +2 -3
- data/lib/concurrent/delay.rb +141 -50
- data/lib/concurrent/edge.rb +30 -0
- data/lib/concurrent/errors.rb +10 -0
- data/lib/concurrent/exchanger.rb +25 -1
- data/lib/concurrent/executor/cached_thread_pool.rb +46 -33
- data/lib/concurrent/executor/executor.rb +46 -299
- data/lib/concurrent/executor/executor_service.rb +521 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +206 -26
- data/lib/concurrent/executor/immediate_executor.rb +9 -9
- data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
- data/lib/concurrent/executor/java_cached_thread_pool.rb +18 -16
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +11 -18
- data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
- data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +9 -18
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +10 -21
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
- data/lib/concurrent/executor/safe_task_executor.rb +5 -4
- data/lib/concurrent/executor/serialized_execution.rb +22 -18
- data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
- data/lib/concurrent/executor/single_thread_executor.rb +32 -21
- data/lib/concurrent/executor/thread_pool_executor.rb +72 -60
- data/lib/concurrent/executor/timer_set.rb +96 -84
- data/lib/concurrent/executors.rb +1 -1
- data/lib/concurrent/future.rb +70 -38
- data/lib/concurrent/immutable_struct.rb +89 -0
- data/lib/concurrent/ivar.rb +152 -60
- data/lib/concurrent/lazy_register.rb +40 -20
- data/lib/concurrent/maybe.rb +226 -0
- data/lib/concurrent/mutable_struct.rb +227 -0
- data/lib/concurrent/mvar.rb +44 -43
- data/lib/concurrent/promise.rb +208 -134
- data/lib/concurrent/scheduled_task.rb +339 -43
- data/lib/concurrent/settable_struct.rb +127 -0
- data/lib/concurrent/synchronization.rb +17 -0
- data/lib/concurrent/synchronization/abstract_object.rb +163 -0
- data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
- data/lib/concurrent/synchronization/condition.rb +53 -0
- data/lib/concurrent/synchronization/java_object.rb +35 -0
- data/lib/concurrent/synchronization/lock.rb +32 -0
- data/lib/concurrent/synchronization/monitor_object.rb +24 -0
- data/lib/concurrent/synchronization/mutex_object.rb +43 -0
- data/lib/concurrent/synchronization/object.rb +78 -0
- data/lib/concurrent/synchronization/rbx_object.rb +75 -0
- data/lib/concurrent/timer_task.rb +87 -100
- data/lib/concurrent/tvar.rb +42 -38
- data/lib/concurrent/utilities.rb +3 -1
- data/lib/concurrent/utility/at_exit.rb +97 -0
- data/lib/concurrent/utility/engine.rb +40 -0
- data/lib/concurrent/utility/monotonic_time.rb +59 -0
- data/lib/concurrent/utility/native_extension_loader.rb +56 -0
- data/lib/concurrent/utility/processor_counter.rb +156 -0
- data/lib/concurrent/utility/timeout.rb +18 -14
- data/lib/concurrent/utility/timer.rb +11 -6
- data/lib/concurrent/version.rb +2 -1
- data/lib/concurrent_ruby.rb +1 -0
- metadata +47 -83
- data/lib/concurrent/actor.rb +0 -103
- data/lib/concurrent/actor/behaviour.rb +0 -70
- data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
- data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
- data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
- data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
- data/lib/concurrent/actor/behaviour/linking.rb +0 -45
- data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
- data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
- data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
- data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
- data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
- data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
- data/lib/concurrent/actor/behaviour/termination.rb +0 -54
- data/lib/concurrent/actor/context.rb +0 -154
- data/lib/concurrent/actor/core.rb +0 -217
- data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
- data/lib/concurrent/actor/envelope.rb +0 -41
- data/lib/concurrent/actor/errors.rb +0 -27
- data/lib/concurrent/actor/internal_delegations.rb +0 -49
- data/lib/concurrent/actor/public_delegations.rb +0 -40
- data/lib/concurrent/actor/reference.rb +0 -81
- data/lib/concurrent/actor/root.rb +0 -37
- data/lib/concurrent/actor/type_check.rb +0 -48
- data/lib/concurrent/actor/utils.rb +0 -10
- data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
- data/lib/concurrent/actor/utils/balancer.rb +0 -42
- data/lib/concurrent/actor/utils/broadcast.rb +0 -52
- data/lib/concurrent/actor/utils/pool.rb +0 -59
- data/lib/concurrent/actress.rb +0 -3
- data/lib/concurrent/agent.rb +0 -209
- data/lib/concurrent/atomic.rb +0 -92
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
- data/lib/concurrent/atomic/synchronization.rb +0 -51
- data/lib/concurrent/channel/buffered_channel.rb +0 -85
- data/lib/concurrent/channel/channel.rb +0 -41
- data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
- data/lib/concurrent/channel/waitable_list.rb +0 -40
- data/lib/concurrent/channels.rb +0 -5
- data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
- data/lib/concurrent/collection/ring_buffer.rb +0 -59
- data/lib/concurrent/collections.rb +0 -3
- data/lib/concurrent/dereferenceable.rb +0 -108
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
- data/lib/concurrent/logging.rb +0 -20
- data/lib/concurrent/obligation.rb +0 -171
- data/lib/concurrent/observable.rb +0 -73
- data/lib/concurrent/options_parser.rb +0 -52
- data/lib/concurrent/utility/processor_count.rb +0 -152
- data/lib/extension_helper.rb +0 -37
@@ -1,58 +1,78 @@
|
|
1
|
-
require 'concurrent/atomic'
|
1
|
+
require 'concurrent/atomic/atomic_reference'
|
2
2
|
require 'concurrent/delay'
|
3
3
|
|
4
|
-
|
5
4
|
module Concurrent
|
6
|
-
|
5
|
+
|
6
|
+
# Hash-like collection that store lazys evaluated values.
|
7
|
+
#
|
7
8
|
# @example
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
9
|
+
# register = Concurrent::LazyRegister.new
|
10
|
+
# #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
|
11
|
+
# register[:key]
|
12
|
+
# #=> nil
|
13
|
+
# register.add(:key) { Concurrent::Actor.spawn!(Actor::AdHoc, :ping) { -> message { message } } }
|
14
|
+
# #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
|
15
|
+
# register[:key]
|
16
|
+
# #=> #<Concurrent::Actor::Reference /ping (Concurrent::Actor::AdHoc)>
|
17
|
+
#
|
18
|
+
# @!macro edge_warning
|
16
19
|
class LazyRegister
|
20
|
+
|
17
21
|
def initialize
|
18
|
-
@data =
|
22
|
+
@data = AtomicReference.new(Hash.new)
|
19
23
|
end
|
20
24
|
|
25
|
+
# Element reference. Retrieves the value object corresponding to the
|
26
|
+
# key object. Returns nil if the key is not found. Raises an exception
|
27
|
+
# if the stored item raised an exception when the block was evaluated.
|
28
|
+
#
|
21
29
|
# @param [Object] key
|
22
|
-
# @return value stored
|
30
|
+
# @return [Object] value stored for the key or nil if the key is not found
|
31
|
+
#
|
23
32
|
# @raise Exception when the initialization block fails
|
24
33
|
def [](key)
|
25
34
|
delay = @data.get[key]
|
26
|
-
delay.value!
|
35
|
+
delay ? delay.value! : nil
|
27
36
|
end
|
28
37
|
|
38
|
+
# Returns true if the given key is present.
|
39
|
+
#
|
29
40
|
# @param [Object] key
|
30
41
|
# @return [true, false] if the key is registered
|
31
42
|
def registered?(key)
|
32
|
-
@data.get.key?
|
43
|
+
@data.get.key?(key)
|
33
44
|
end
|
34
45
|
|
35
46
|
alias_method :key?, :registered?
|
47
|
+
alias_method :has_key?, :registered?
|
36
48
|
|
49
|
+
# Element assignment. Associates the value given by value with the
|
50
|
+
# key given by key.
|
51
|
+
#
|
37
52
|
# @param [Object] key
|
38
53
|
# @yield the object to store under the key
|
39
|
-
#
|
54
|
+
#
|
55
|
+
# @return [LazyRegister] self
|
40
56
|
def register(key, &block)
|
41
|
-
delay = Delay.new(&block)
|
42
|
-
@data.update { |h| h.merge
|
57
|
+
delay = Delay.new(executor: :immediate, &block)
|
58
|
+
@data.update { |h| h.merge(key => delay) }
|
43
59
|
self
|
44
60
|
end
|
45
61
|
|
46
62
|
alias_method :add, :register
|
63
|
+
alias_method :store, :register
|
47
64
|
|
48
|
-
# Un-registers the object under key, realized or not
|
49
|
-
#
|
65
|
+
# Un-registers the object under key, realized or not.
|
66
|
+
#
|
50
67
|
# @param [Object] key
|
68
|
+
#
|
69
|
+
# @return [LazyRegister] self
|
51
70
|
def unregister(key)
|
52
71
|
@data.update { |h| h.dup.tap { |j| j.delete(key) } }
|
53
72
|
self
|
54
73
|
end
|
55
74
|
|
56
75
|
alias_method :remove, :unregister
|
76
|
+
alias_method :delete, :unregister
|
57
77
|
end
|
58
78
|
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# A `Maybe` encapsulates an optional value. A `Maybe` either contains a value
|
4
|
+
# of (represented as `Just`), or it is empty (represented as `Nothing`). Using
|
5
|
+
# `Maybe` is a good way to deal with errors or exceptional cases without
|
6
|
+
# resorting to drastic measures such as exceptions.
|
7
|
+
#
|
8
|
+
# `Maybe` is a replacement for the use of `nil` with better type checking.
|
9
|
+
#
|
10
|
+
# For compatibility with {Concurrent::Concern::Obligation} the predicate and
|
11
|
+
# accessor methods are aliased as `fulfilled?`, `rejected?`, `value`, and
|
12
|
+
# `reason`.
|
13
|
+
#
|
14
|
+
# ## Motivation
|
15
|
+
#
|
16
|
+
# A common pattern in languages with pattern matching, such as Erlang and
|
17
|
+
# Haskell, is to return *either* a value *or* an error from a function
|
18
|
+
# Consider this Erlang code:
|
19
|
+
#
|
20
|
+
# ```erlang
|
21
|
+
# case file:consult("data.dat") of
|
22
|
+
# {ok, Terms} -> do_something_useful(Terms);
|
23
|
+
# {error, Reason} -> lager:error(Reason)
|
24
|
+
# end.
|
25
|
+
# ```
|
26
|
+
#
|
27
|
+
# In this example the standard library function `file:consult` returns a
|
28
|
+
# [tuple](http://erlang.org/doc/reference_manual/data_types.html#id69044)
|
29
|
+
# with two elements: an [atom](http://erlang.org/doc/reference_manual/data_types.html#id64134)
|
30
|
+
# (similar to a ruby symbol) and a variable containing ancillary data. On
|
31
|
+
# success it returns the atom `ok` and the data from the file. On failure it
|
32
|
+
# returns `error` and a string with an explanation of the problem. With this
|
33
|
+
# pattern there is no ambiguity regarding success or failure. If the file is
|
34
|
+
# empty the return value cannot be misinterpreted as an error. And when an
|
35
|
+
# error occurs the return value provides useful information.
|
36
|
+
#
|
37
|
+
# In Ruby we tend to return `nil` when an error occurs or else we raise an
|
38
|
+
# exception. Both of these idioms are problematic. Returning `nil` is
|
39
|
+
# ambiguous because `nil` may also be a valid value. It also lacks
|
40
|
+
# information pertaining to the nature of the error. Raising an exception
|
41
|
+
# is both expensive and usurps the normal flow of control. All of these
|
42
|
+
# problems can be solved with the use of a `Maybe`.
|
43
|
+
#
|
44
|
+
# A `Maybe` is unambiguous with regard to whether or not it contains a value.
|
45
|
+
# When `Just` it contains a value, when `Nothing` it does not. When `Just`
|
46
|
+
# the value it contains may be `nil`, which is perfectly valid. When
|
47
|
+
# `Nothing` the reason for the lack of a value is contained as well. The
|
48
|
+
# previous Erlang example can be duplicated in Ruby in a principled way by
|
49
|
+
# having functions return `Maybe` objects:
|
50
|
+
#
|
51
|
+
# ```ruby
|
52
|
+
# result = MyFileUtils.consult("data.dat") # returns a Maybe
|
53
|
+
# if result.just?
|
54
|
+
# do_something_useful(result.value) # or result.just
|
55
|
+
# else
|
56
|
+
# logger.error(result.reason) # or result.nothing
|
57
|
+
# end
|
58
|
+
# ```
|
59
|
+
#
|
60
|
+
# @example Returning a Maybe from a Function
|
61
|
+
# module MyFileUtils
|
62
|
+
# def self.consult(path)
|
63
|
+
# file = File.open(path, 'r')
|
64
|
+
# Concurrent::Maybe.just(file.read)
|
65
|
+
# rescue => ex
|
66
|
+
# return Concurrent::Maybe.nothing(ex)
|
67
|
+
# ensure
|
68
|
+
# file.close if file
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# maybe = MyFileUtils.consult('bogus.file')
|
73
|
+
# maybe.just? #=> false
|
74
|
+
# maybe.nothing? #=> true
|
75
|
+
# maybe.reason #=> #<Errno::ENOENT: No such file or directory @ rb_sysopen - bogus.file>
|
76
|
+
#
|
77
|
+
# maybe = MyFileUtils.consult('README.md')
|
78
|
+
# maybe.just? #=> true
|
79
|
+
# maybe.nothing? #=> false
|
80
|
+
# maybe.value #=> "# Concurrent Ruby\n[![Gem Version..."
|
81
|
+
#
|
82
|
+
# @example Using Maybe with a Block
|
83
|
+
# result = Concurrent::Maybe.from do
|
84
|
+
# Client.find(10) # Client is an ActiveRecord model
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# # -- if the record was found
|
88
|
+
# result.just? #=> true
|
89
|
+
# result.value #=> #<Client id: 10, first_name: "Ryan">
|
90
|
+
#
|
91
|
+
# # -- if the record was not found
|
92
|
+
# result.just? #=> false
|
93
|
+
# result.reason #=> ActiveRecord::RecordNotFound
|
94
|
+
#
|
95
|
+
# @example Using Maybe with the Null Object Pattern
|
96
|
+
# # In a Rails controller...
|
97
|
+
# result = ClientService.new(10).find # returns a Maybe
|
98
|
+
# render json: result.or(NullClient.new)
|
99
|
+
#
|
100
|
+
# @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html Haskell Data.Maybe
|
101
|
+
# @see https://github.com/purescript/purescript-maybe/blob/master/docs/Data.Maybe.md PureScript Data.Maybe
|
102
|
+
class Maybe
|
103
|
+
include Comparable
|
104
|
+
|
105
|
+
# Indicates that the given attribute has not been set.
|
106
|
+
# When `Just` the {#nothing} getter will return `NONE`.
|
107
|
+
# When `Nothing` the {#just} getter will return `NONE`.
|
108
|
+
NONE = Object.new.freeze
|
109
|
+
|
110
|
+
# The value of a `Maybe` when `Just`. Will be `NONE` when `Nothing`.
|
111
|
+
attr_reader :just
|
112
|
+
|
113
|
+
# The reason for the `Maybe` when `Nothing`. Will be `NONE` when `Just`.
|
114
|
+
attr_reader :nothing
|
115
|
+
|
116
|
+
private_class_method :new
|
117
|
+
|
118
|
+
# Create a new `Maybe` using the given block.
|
119
|
+
#
|
120
|
+
# Runs the given block passing all function arguments to the block as block
|
121
|
+
# arguments. If the block runs to completion without raising an exception
|
122
|
+
# a new `Just` is created with the value set to the return value of the
|
123
|
+
# block. If the block raises an exception a new `Nothing` is created with
|
124
|
+
# the reason being set to the raised exception.
|
125
|
+
#
|
126
|
+
# @param [Array<Object>] args Zero or more arguments to pass to the block.
|
127
|
+
# @yield The block from which to create a new `Maybe`.
|
128
|
+
# @yieldparam [Array<Object>] args Zero or more block arguments passed as
|
129
|
+
# arguments to the function.
|
130
|
+
#
|
131
|
+
# @return [Maybe] The newly created object.
|
132
|
+
#
|
133
|
+
# @raise [ArgumentError] when no block given.
|
134
|
+
def self.from(*args)
|
135
|
+
raise ArgumentError.new('no block given') unless block_given?
|
136
|
+
begin
|
137
|
+
value = yield(*args)
|
138
|
+
return new(value, NONE)
|
139
|
+
rescue => ex
|
140
|
+
return new(NONE, ex)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Create a new `Just` with the given value.
|
145
|
+
#
|
146
|
+
# @param [Object] value The value to set for the new `Maybe` object.
|
147
|
+
#
|
148
|
+
# @return [Maybe] The newly created object.
|
149
|
+
def self.just(value)
|
150
|
+
return new(value, NONE)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Create a new `Nothing` with the given (optional) reason.
|
154
|
+
#
|
155
|
+
# @param [Exception] error The reason to set for the new `Maybe` object.
|
156
|
+
# When given a string a new `StandardError` will be created with the
|
157
|
+
# argument as the message. When no argument is given a new
|
158
|
+
# `StandardError` with an empty message will be created.
|
159
|
+
#
|
160
|
+
# @return [Maybe] The newly created object.
|
161
|
+
def self.nothing(error = '')
|
162
|
+
if error.is_a?(Exception)
|
163
|
+
nothing = error
|
164
|
+
else
|
165
|
+
nothing = StandardError.new(error.to_s)
|
166
|
+
end
|
167
|
+
return new(NONE, nothing)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Is this `Maybe` a `Just` (successfully fulfilled with a value)?
|
171
|
+
#
|
172
|
+
# @return [Boolean] True if `Just` or false if `Nothing`.
|
173
|
+
def just?
|
174
|
+
! nothing?
|
175
|
+
end
|
176
|
+
alias :fulfilled? :just?
|
177
|
+
|
178
|
+
# Is this `Maybe` a `nothing` (rejected with an exception upon fulfillment)?
|
179
|
+
#
|
180
|
+
# @return [Boolean] True if `Nothing` or false if `Just`.
|
181
|
+
def nothing?
|
182
|
+
@nothing != NONE
|
183
|
+
end
|
184
|
+
alias :rejected? :nothing?
|
185
|
+
|
186
|
+
alias :value :just
|
187
|
+
|
188
|
+
alias :reason :nothing
|
189
|
+
|
190
|
+
# Comparison operator.
|
191
|
+
#
|
192
|
+
# @return [Integer] 0 if self and other are both `Nothing`;
|
193
|
+
# -1 if self is `Nothing` and other is `Just`;
|
194
|
+
# 1 if self is `Just` and other is nothing;
|
195
|
+
# `self.just <=> other.just` if both self and other are `Just`.
|
196
|
+
def <=>(other)
|
197
|
+
if nothing?
|
198
|
+
other.nothing? ? 0 : -1
|
199
|
+
else
|
200
|
+
other.nothing? ? 1 : just <=> other.just
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Return either the value of self or the given default value.
|
205
|
+
#
|
206
|
+
# @return [Object] The value of self when `Just`; else the given default.
|
207
|
+
def or(other)
|
208
|
+
just? ? just : other
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Create a new `Maybe` with the given attributes.
|
214
|
+
#
|
215
|
+
# @param [Object] just The value when `Just` else `NONE`.
|
216
|
+
# @param [Exception, Object] nothing The exception when `Nothing` else `NONE`.
|
217
|
+
#
|
218
|
+
# @return [Maybe] The new `Maybe`.
|
219
|
+
#
|
220
|
+
# @!visibility private
|
221
|
+
def initialize(just, nothing)
|
222
|
+
@just = just
|
223
|
+
@nothing = nothing
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'concurrent/synchronization/abstract_struct'
|
2
|
+
require 'concurrent/synchronization'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# An thread-safe variation of Ruby's standard `Struct`. Values can be set at
|
7
|
+
# construction or safely changed at any time during the object's lifecycle.
|
8
|
+
#
|
9
|
+
# @see http://ruby-doc.org/core-2.2.0/Struct.html Ruby standard library `Struct`
|
10
|
+
module MutableStruct
|
11
|
+
include Synchronization::AbstractStruct
|
12
|
+
|
13
|
+
# @!macro [new] struct_new
|
14
|
+
#
|
15
|
+
# Factory for creating new struct classes.
|
16
|
+
#
|
17
|
+
# ```
|
18
|
+
# new([class_name] [, member_name]+>) -> StructClass click to toggle source
|
19
|
+
# new([class_name] [, member_name]+>) {|StructClass| block } -> StructClass
|
20
|
+
# new(value, ...) -> obj
|
21
|
+
# StructClass[value, ...] -> obj
|
22
|
+
# ```
|
23
|
+
#
|
24
|
+
# The first two forms are used to create a new struct subclass `class_name`
|
25
|
+
# that can contain a value for each member_name . This subclass can be
|
26
|
+
# used to create instances of the structure like any other Class .
|
27
|
+
#
|
28
|
+
# If the `class_name` is omitted an anonymous struct class will be created.
|
29
|
+
# Otherwise, the name of this struct will appear as a constant in the struct class,
|
30
|
+
# so it must be unique for all structs under this base class and must start with a
|
31
|
+
# capital letter. Assigning a struct class to a constant also gives the class
|
32
|
+
# the name of the constant.
|
33
|
+
#
|
34
|
+
# If a block is given it will be evaluated in the context of `StructClass`, passing
|
35
|
+
# the created class as a parameter. This is the recommended way to customize a struct.
|
36
|
+
# Subclassing an anonymous struct creates an extra anonymous class that will never be used.
|
37
|
+
#
|
38
|
+
# The last two forms create a new instance of a struct subclass. The number of value
|
39
|
+
# parameters must be less than or equal to the number of attributes defined for the
|
40
|
+
# struct. Unset parameters default to nil. Passing more parameters than number of attributes
|
41
|
+
# will raise an `ArgumentError`.
|
42
|
+
#
|
43
|
+
# @see http://ruby-doc.org/core-2.2.0/Struct.html#method-c-new Ruby standard library `Struct#new`
|
44
|
+
|
45
|
+
# @!macro [attach] struct_values
|
46
|
+
#
|
47
|
+
# Returns the values for this struct as an Array.
|
48
|
+
#
|
49
|
+
# @return [Array] the values for this struct
|
50
|
+
#
|
51
|
+
def values
|
52
|
+
synchronize { ns_values }
|
53
|
+
end
|
54
|
+
alias_method :to_a, :values
|
55
|
+
|
56
|
+
# @!macro [attach] struct_values_at
|
57
|
+
#
|
58
|
+
# Returns the struct member values for each selector as an Array.
|
59
|
+
#
|
60
|
+
# A selector may be either an Integer offset or a Range of offsets (as in `Array#values_at`).
|
61
|
+
#
|
62
|
+
# @param [Fixnum, Range] indexes the index(es) from which to obatin the values (in order)
|
63
|
+
def values_at(*indexes)
|
64
|
+
synchronize { ns_values_at(indexes) }
|
65
|
+
end
|
66
|
+
|
67
|
+
# @!macro [attach] struct_inspect
|
68
|
+
#
|
69
|
+
# Describe the contents of this struct in a string.
|
70
|
+
#
|
71
|
+
# @return [String] the contents of this struct in a string
|
72
|
+
def inspect
|
73
|
+
synchronize { ns_inspect }
|
74
|
+
end
|
75
|
+
alias_method :to_s, :inspect
|
76
|
+
|
77
|
+
# @!macro [attach] struct_merge
|
78
|
+
#
|
79
|
+
# Returns a new struct containing the contents of `other` and the contents
|
80
|
+
# of `self`. If no block is specified, the value for entries with duplicate
|
81
|
+
# keys will be that of `other`. Otherwise the value for each duplicate key
|
82
|
+
# is determined by calling the block with the key, its value in `self` and
|
83
|
+
# its value in `other`.
|
84
|
+
#
|
85
|
+
# @param [Hash] other the hash from which to set the new values
|
86
|
+
# @yield an options block for resolving duplicate keys
|
87
|
+
# @yieldparam [String, Symbol] member the name of the member which is duplicated
|
88
|
+
# @yieldparam [Object] selfvalue the value of the member in `self`
|
89
|
+
# @yieldparam [Object] othervalue the value of the member in `other`
|
90
|
+
#
|
91
|
+
# @return [Synchronization::AbstractStruct] a new struct with the new values
|
92
|
+
#
|
93
|
+
# @raise [ArgumentError] of given a member that is not defined in the struct
|
94
|
+
def merge(other, &block)
|
95
|
+
synchronize { ns_merge(other, &block) }
|
96
|
+
end
|
97
|
+
|
98
|
+
# @!macro [attach] struct_to_h
|
99
|
+
#
|
100
|
+
# Returns a hash containing the names and values for the struct’s members.
|
101
|
+
#
|
102
|
+
# @return [Hash] the names and values for the struct’s members
|
103
|
+
def to_h
|
104
|
+
synchronize { ns_to_h }
|
105
|
+
end
|
106
|
+
|
107
|
+
# @!macro [attach] struct_get
|
108
|
+
#
|
109
|
+
# Attribute Reference
|
110
|
+
#
|
111
|
+
# @param [Symbol, String, Integer] member the string or symbol name of the memeber
|
112
|
+
# for which to obtain the value or the member's index
|
113
|
+
#
|
114
|
+
# @return [Object] the value of the given struct member or the member at the given index.
|
115
|
+
#
|
116
|
+
# @raise [NameError] if the member does not exist
|
117
|
+
# @raise [IndexError] if the index is out of range.
|
118
|
+
def [](member)
|
119
|
+
synchronize { ns_get(member) }
|
120
|
+
end
|
121
|
+
|
122
|
+
# @!macro [attach] struct_equality
|
123
|
+
#
|
124
|
+
# Equality
|
125
|
+
#
|
126
|
+
# @return [Boolean] true if other has the same struct subclass and has
|
127
|
+
# equal member values (according to `Object#==`)
|
128
|
+
def ==(other)
|
129
|
+
synchronize { ns_equality(other) }
|
130
|
+
end
|
131
|
+
|
132
|
+
# @!macro [attach] struct_each
|
133
|
+
#
|
134
|
+
# Yields the value of each struct member in order. If no block is given
|
135
|
+
# an enumerator is returned.
|
136
|
+
#
|
137
|
+
# @yield the operation to be performed on each struct member
|
138
|
+
# @yieldparam [Object] value each struct value (in order)
|
139
|
+
def each(&block)
|
140
|
+
return enum_for(:each) unless block_given?
|
141
|
+
synchronize { ns_each(&block) }
|
142
|
+
end
|
143
|
+
|
144
|
+
# @!macro [attach] struct_each_pair
|
145
|
+
#
|
146
|
+
# Yields the name and value of each struct member in order. If no block is
|
147
|
+
# given an enumerator is returned.
|
148
|
+
#
|
149
|
+
# @yield the operation to be performed on each struct member/value pair
|
150
|
+
# @yieldparam [Object] member each struct member (in order)
|
151
|
+
# @yieldparam [Object] value each struct value (in order)
|
152
|
+
def each_pair(&block)
|
153
|
+
return enum_for(:each_pair) unless block_given?
|
154
|
+
synchronize { ns_each_pair(&block) }
|
155
|
+
end
|
156
|
+
|
157
|
+
# @!macro [attach] struct_select
|
158
|
+
#
|
159
|
+
# Yields each member value from the struct to the block and returns an Array
|
160
|
+
# containing the member values from the struct for which the given block
|
161
|
+
# returns a true value (equivalent to `Enumerable#select`).
|
162
|
+
#
|
163
|
+
# @yield the operation to be performed on each struct member
|
164
|
+
# @yieldparam [Object] value each struct value (in order)
|
165
|
+
#
|
166
|
+
# @return [Array] an array containing each value for which the block returns true
|
167
|
+
def select(&block)
|
168
|
+
return enum_for(:select) unless block_given?
|
169
|
+
synchronize { ns_select(&block) }
|
170
|
+
end
|
171
|
+
|
172
|
+
# @!macro [new] struct_set
|
173
|
+
#
|
174
|
+
# Attribute Assignment
|
175
|
+
#
|
176
|
+
# Sets the value of the given struct member or the member at the given index.
|
177
|
+
#
|
178
|
+
# @param [Symbol, String, Integer] member the string or symbol name of the memeber
|
179
|
+
# for which to obtain the value or the member's index
|
180
|
+
#
|
181
|
+
# @return [Object] the value of the given struct member or the member at the given index.
|
182
|
+
#
|
183
|
+
# @raise [NameError] if the name does not exist
|
184
|
+
# @raise [IndexError] if the index is out of range.
|
185
|
+
def []=(member, value)
|
186
|
+
if member.is_a? Integer
|
187
|
+
if member >= @values.length
|
188
|
+
raise IndexError.new("offset #{member} too large for struct(size:#{@values.length})")
|
189
|
+
end
|
190
|
+
synchronize { @values[member] = value }
|
191
|
+
else
|
192
|
+
send("#{member}=", value)
|
193
|
+
end
|
194
|
+
rescue NoMethodError
|
195
|
+
raise NameError.new("no member '#{member}' in struct")
|
196
|
+
end
|
197
|
+
|
198
|
+
# @!macro struct_new
|
199
|
+
def self.new(*args, &block)
|
200
|
+
clazz_name = nil
|
201
|
+
if args.length == 0
|
202
|
+
raise ArgumentError.new('wrong number of arguments (0 for 1+)')
|
203
|
+
elsif args.length > 0 && args.first.is_a?(String)
|
204
|
+
clazz_name = args.shift
|
205
|
+
end
|
206
|
+
FACTORY.define_struct(clazz_name, args, &block)
|
207
|
+
end
|
208
|
+
|
209
|
+
FACTORY = Class.new(Synchronization::Object) do
|
210
|
+
def define_struct(name, members, &block)
|
211
|
+
synchronize do
|
212
|
+
clazz = Synchronization::AbstractStruct.define_struct_class(MutableStruct, Synchronization::Object, name, members, &block)
|
213
|
+
members.each_with_index do |member, index|
|
214
|
+
clazz.send(:define_method, member) do
|
215
|
+
synchronize { @values[index] }
|
216
|
+
end
|
217
|
+
clazz.send(:define_method, "#{member}=") do |value|
|
218
|
+
synchronize { @values[index] = value }
|
219
|
+
end
|
220
|
+
end
|
221
|
+
clazz
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end.new
|
225
|
+
private_constant :FACTORY
|
226
|
+
end
|
227
|
+
end
|