weak 0.1.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 +7 -0
- data/CHANGELOG.md +15 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/LICENSE.txt +21 -0
- data/README.md +232 -0
- data/lib/weak/map/abstract_strong_keys.rb +87 -0
- data/lib/weak/map/deletable.rb +65 -0
- data/lib/weak/map/strong_keys.rb +186 -0
- data/lib/weak/map/strong_secondary_keys.rb +229 -0
- data/lib/weak/map/weak_keys.rb +134 -0
- data/lib/weak/map/weak_keys_with_delete.rb +126 -0
- data/lib/weak/map.rb +714 -0
- data/lib/weak/set/strong_keys.rb +123 -0
- data/lib/weak/set/strong_secondary_keys.rb +154 -0
- data/lib/weak/set/weak_keys.rb +107 -0
- data/lib/weak/set/weak_keys_with_delete.rb +94 -0
- data/lib/weak/set.rb +749 -0
- data/lib/weak/version.rb +14 -0
- data/lib/weak.rb +45 -0
- metadata +65 -0
data/lib/weak/map.rb
ADDED
@@ -0,0 +1,714 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) Holger Just
|
4
|
+
#
|
5
|
+
# This software may be modified and distributed under the terms
|
6
|
+
# of the MIT license. See the LICENSE.txt file for details.
|
7
|
+
|
8
|
+
require_relative "map/weak_keys_with_delete"
|
9
|
+
require_relative "map/weak_keys"
|
10
|
+
require_relative "map/strong_keys"
|
11
|
+
require_relative "map/strong_secondary_keys"
|
12
|
+
|
13
|
+
##
|
14
|
+
module Weak
|
15
|
+
# `Weak::Map` behaves similar to a `Hash` or an `ObjectSpace::WeakMap` in Ruby
|
16
|
+
# (aka. MRI, aka. YARV). Both keys and values are weakly referenceed, allowing
|
17
|
+
# either of them to be independently garbage collected. If either the key or
|
18
|
+
# the value of a pair is garbage collected, the entire pair will be removed
|
19
|
+
# from the `Weak::Map`.
|
20
|
+
#
|
21
|
+
# {Weak::Map} uses `ObjectSpace::WeakMap` as storage, so you must note the
|
22
|
+
# following points:
|
23
|
+
#
|
24
|
+
# - Equality of both keys and values is determined strictly by their object
|
25
|
+
# identity instead of `Object#eql?` or `Object#hash` as the `Hash` class
|
26
|
+
# does by default.
|
27
|
+
# - Keys and values can be freely changed without affecting the map.
|
28
|
+
# - Keys and values can be freely garbage collected by Ruby. A key-value pair
|
29
|
+
# will be removed from the map automatically if theoer the key or the value
|
30
|
+
# is garbage collected.
|
31
|
+
# - The order of key-value pairs in the map is non-deterministic. Insertion
|
32
|
+
# order is not preserved.
|
33
|
+
#
|
34
|
+
# Note that {Weak::Map} is not inherently thread-safe. When accessing a
|
35
|
+
# {Weak::Map} from multiple threads or fibers, you MUST use a mutex or another
|
36
|
+
# locking mechanism.
|
37
|
+
#
|
38
|
+
# ## Implementation Details
|
39
|
+
#
|
40
|
+
# The various Ruby implementations and versions show quite diverse behavior in
|
41
|
+
# their respective `ObjectSpace::WeakMap` implementations. To provide a
|
42
|
+
# unified behavior on all implementations, we use different storage
|
43
|
+
# strategies:
|
44
|
+
#
|
45
|
+
# - Ruby (aka. MRI, aka. YARV) >= 3.3 has an `ObjectSpace::WeakMap` with weak
|
46
|
+
# keys and weak values and the ability to delete elements from it. This
|
47
|
+
# allows a straight-forward implementation in
|
48
|
+
# {Weak::Map::WeakKeysWithDelete}.
|
49
|
+
# - Ruby (aka. MRI, aka. YARV) < 3.3 has an `ObjectSpace::WeakMap` with weak
|
50
|
+
# keys and weak values but does not allow to directly delete entries. We
|
51
|
+
# emulate this with special garbage-collectible values in
|
52
|
+
# {Weak::Map::WeakKeys}.
|
53
|
+
# - JRuby >= 9.4.6.0 and TruffleRuby >= 22 have an `ObjectSpace::WeakMap` with
|
54
|
+
# strong keys and weak values. To allow both keys and values to be garbage
|
55
|
+
# collected, we can't use the actual object as a key in a single
|
56
|
+
# `ObjectSpace::WeakMap`. Instead, we use a sepate `WeakMap` for keys and
|
57
|
+
# values which in turn use the key's `object_id` as a key. As
|
58
|
+
# these `ObjectSpace::WeakMap` objects also do not allow to delete entries,
|
59
|
+
# we emulate deletion with special garbage-collectible values as above. This
|
60
|
+
# is implemented in {Weak::Map::StrongKeys}.
|
61
|
+
# - JRuby < 9.4.6.0 has a similar `ObjectSpace::WeakMap` as newer JRuby
|
62
|
+
# versions with strong keys and weak values. However generally in JRuby,
|
63
|
+
# Integer values (including object_ids) can have multiple different object
|
64
|
+
# representations in memory and are not necessarily equal to each other when
|
65
|
+
# used as keys in an `ObjectSpace::WeakMap`. As a workaround we use an
|
66
|
+
# indirect implementation with a secondary lookup table for the map keys in
|
67
|
+
# for both stored keys and values {Weak::Map::StrongSecondaryKeys}.
|
68
|
+
#
|
69
|
+
# The required strategy is selected automatically based in the running
|
70
|
+
# Ruby. The external behavior is the same for all implementations.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# require "weak/map"
|
74
|
+
#
|
75
|
+
# map = Weak::Map.new
|
76
|
+
# map[:key] = "a value"
|
77
|
+
# map[:key]
|
78
|
+
# # => "a value"
|
79
|
+
class Map
|
80
|
+
include Enumerable
|
81
|
+
|
82
|
+
# We try to find the best implementation strategy based on the current Ruby
|
83
|
+
# engine and version. The chosen `STRATEGY` is included into the {Weak::Map}
|
84
|
+
# class.
|
85
|
+
STRATEGY = [
|
86
|
+
Weak::Map::WeakKeysWithDelete,
|
87
|
+
Weak::Map::WeakKeys,
|
88
|
+
Weak::Map::StrongKeys,
|
89
|
+
Weak::Map::StrongSecondaryKeys
|
90
|
+
].find(&:usable?)
|
91
|
+
|
92
|
+
include STRATEGY
|
93
|
+
|
94
|
+
############################################################################
|
95
|
+
# Here follows the documentation of strategy-specific methods which are
|
96
|
+
# implemented in one of the include modules depending on the current Ruby.
|
97
|
+
|
98
|
+
# @!macro _note_object_equality
|
99
|
+
# @note {Weak::Map} does not test member equality with `==` or `eql?`.
|
100
|
+
# Instead, it always checks strict object equality, so that, e.g.,
|
101
|
+
# different String keys are not considered equal, even if they may
|
102
|
+
# contain the same content.
|
103
|
+
#
|
104
|
+
# @!macro weak_map_accessor_read
|
105
|
+
# @param key [Object] the key for the requested value
|
106
|
+
# @return [Object] the value associated with the given `key`, if found. If
|
107
|
+
# `key` is not found, returns the default value, i.e. the value returned
|
108
|
+
# by the default proc (if defined) or the `default` value (which is
|
109
|
+
# initially `nil`.)
|
110
|
+
# @!macro _note_object_equality
|
111
|
+
|
112
|
+
# @!macro weak_map_accessor_write
|
113
|
+
# Associates the given `value` with the given `key`; returns `value`. If
|
114
|
+
# the given `key` exists, replaces its value with the given `value`.
|
115
|
+
#
|
116
|
+
# @param key [Object] the key for the set key-value pair
|
117
|
+
# @param value [Object] the value of the set key-value pair
|
118
|
+
# @return [Object] the given `value`
|
119
|
+
# @!macro _note_object_equality
|
120
|
+
|
121
|
+
# @!macro weak_map_method_clear
|
122
|
+
# Removes all elements and returns `self`
|
123
|
+
#
|
124
|
+
# @return [self]
|
125
|
+
|
126
|
+
# @!macro weak_map_method_delete
|
127
|
+
# Deletes the key-value pair and returns the value from `self` whose key
|
128
|
+
# is equal to `key`. If the key is not found, it returns `nil`. If the
|
129
|
+
# optional block is given and the key is not found, pass in the key and
|
130
|
+
# return the result of the block.
|
131
|
+
#
|
132
|
+
# @param key [Object] the key to delete
|
133
|
+
# @return [Object, nil] the value associated with the given `key`, or the
|
134
|
+
# result of the optional block if given the key was not found, or `nil`
|
135
|
+
# if the key was not found and no block was given.
|
136
|
+
# @yield [key]
|
137
|
+
# @yieldparam key [Object] the given `key` if it was not part of the map
|
138
|
+
# @!macro _note_object_equality
|
139
|
+
|
140
|
+
# @!macro weak_map_method_each_pair
|
141
|
+
# Calls the given block once for each live key in `self`, passing the key
|
142
|
+
# and value as parameters. Returns the weak map itself.
|
143
|
+
#
|
144
|
+
# If no block is given, an `Enumerator` is returned instead.
|
145
|
+
#
|
146
|
+
# @yield [key, value] calls the given block once for each key in `self`
|
147
|
+
# @yieldparam key [Object] the key of the current key-value pair
|
148
|
+
# @yieldparam value [Object] the value of the current key-value pair
|
149
|
+
# @return [self, Enumerator] `self` if a block was given or an
|
150
|
+
# `Enumerator` if no block was given.
|
151
|
+
|
152
|
+
# @!macro weak_map_method_each_key
|
153
|
+
# Calls the given block once for each live key in `self`, passing the key
|
154
|
+
# as a parameter. Returns the weak map itself.
|
155
|
+
#
|
156
|
+
# If no block is given, an `Enumerator` is returned instead.
|
157
|
+
#
|
158
|
+
# @yield [key] calls the given block once for each key in `self`
|
159
|
+
# @yieldparam key [Object] the key of the current key-value pair
|
160
|
+
# @return [self, Enumerator] `self` if a block was given or an
|
161
|
+
# `Enumerator` if no block was given.
|
162
|
+
|
163
|
+
# @!macro weak_map_method_each_value
|
164
|
+
# Calls the given block once for each live key `self`, passing the live
|
165
|
+
# value associated with the key as a parameter. Returns the weak map
|
166
|
+
# itself.
|
167
|
+
#
|
168
|
+
# If no block is given, an `Enumerator` is returned instead.
|
169
|
+
#
|
170
|
+
# @yield [value] calls the given block once for each key in `self`
|
171
|
+
# @yieldparam value [Object] the value of the current key-value pair
|
172
|
+
# @return [self, Enumerator] `self` if a block was given or an
|
173
|
+
# `Enumerator` if no block was given.
|
174
|
+
|
175
|
+
# @!macro weak_map_method_fetch
|
176
|
+
# Returns a value from the hash for the given `key`. If the key can't be
|
177
|
+
# found, there are several options: With no other arguments, it will raise
|
178
|
+
# a `KeyError` exception; if `default` is given, then that value will be
|
179
|
+
# returned; if the optional code block is specified, then it will be
|
180
|
+
# called and its result returned.
|
181
|
+
#
|
182
|
+
# @param key [Object] the key for the requested value
|
183
|
+
# @param default [Object] a value to return if there is no value at `key`
|
184
|
+
# in the hash
|
185
|
+
# @yield [key] if no value was set at `key`, no `default` value was given,
|
186
|
+
# and a block was given, we call the block and return its value
|
187
|
+
# @yieldparam key [String] the given `key`
|
188
|
+
# @return [Object] the value for the given `key` if present in the map. If
|
189
|
+
# the key was not found, we return the `default` value or the value of
|
190
|
+
# the given block.
|
191
|
+
# @raise [KeyError] if the key can not be found and no block or `default`
|
192
|
+
# value was provided
|
193
|
+
# @!macro _note_object_equality
|
194
|
+
|
195
|
+
# @!macro weak_map_method_include_question
|
196
|
+
# @param key [Object] a possible key
|
197
|
+
# @return [Bool] `true` if the given key is included in `self` and has an
|
198
|
+
# associated live value, `false` otherwise
|
199
|
+
# @!macro _note_object_equality
|
200
|
+
|
201
|
+
# @!macro weak_map_method_keys
|
202
|
+
# @return [Array] an `Array` containing all keys of the map for which we
|
203
|
+
# have a valid value. Keys with garbage-collected values are excluded.
|
204
|
+
# @note In contrast to a `Hash`, `Weak::Map`s do not necessarily retain
|
205
|
+
# insertion order.
|
206
|
+
# @see Weak::Map#values
|
207
|
+
|
208
|
+
# @!macro weak_map_method_prune
|
209
|
+
# Cleanup data structures from the map to remove data associated with
|
210
|
+
# deleted or garbage collected keys and/or values. This method may be
|
211
|
+
# called automatically for some {Weak::Map} operations.
|
212
|
+
#
|
213
|
+
# @return [self]
|
214
|
+
|
215
|
+
# @!macro weak_map_method_size
|
216
|
+
# @return [Integer] the number of live key-value pairs in `self`
|
217
|
+
|
218
|
+
# @!macro weak_map_method_values
|
219
|
+
# @return [Array] an `Array` containing all values of the map for which we
|
220
|
+
# have a valid key. Values with garbage-collected keys are excluded.
|
221
|
+
# @note In contrast to a `Hash`, `Weak::Map`s do not necessarily retain
|
222
|
+
# insertion order.
|
223
|
+
# @see Weak::Map#keys
|
224
|
+
|
225
|
+
############################################################################
|
226
|
+
|
227
|
+
# @!method [](key)
|
228
|
+
# @!macro weak_map_accessor_read
|
229
|
+
|
230
|
+
# @!method []=(key, value)
|
231
|
+
# @!macro weak_map_accessor_write
|
232
|
+
|
233
|
+
# @!method clear
|
234
|
+
# @!macro weak_map_method_clear
|
235
|
+
|
236
|
+
# @!method delete(key = UNDEFINED)
|
237
|
+
# @!macro weak_map_method_delete
|
238
|
+
|
239
|
+
# @!method each_pair
|
240
|
+
# @!macro weak_map_method_each_pair
|
241
|
+
|
242
|
+
# @!method each_key
|
243
|
+
# @!macro weak_map_method_each_key
|
244
|
+
|
245
|
+
# @!method each_value
|
246
|
+
# @!macro weak_map_method_each_value
|
247
|
+
|
248
|
+
# @!method fetch(key, default = UNDEFINED, &block)
|
249
|
+
# @!macro weak_map_method_fetch
|
250
|
+
|
251
|
+
# @!method include?(key)
|
252
|
+
# @!macro weak_map_method_include_question
|
253
|
+
|
254
|
+
# @!method keys
|
255
|
+
# @!macro weak_map_method_keys
|
256
|
+
|
257
|
+
# @!method prune
|
258
|
+
# @!macro weak_map_method_prune
|
259
|
+
|
260
|
+
# @!method size
|
261
|
+
# @!macro weak_map_method_size
|
262
|
+
|
263
|
+
# @!method values
|
264
|
+
# @!macro weak_map_method_values
|
265
|
+
|
266
|
+
############################################################################
|
267
|
+
|
268
|
+
# The same value as `Set::InspectKey`. This is used as a key in
|
269
|
+
# `Thread.current` in {#inspect} to resolve object loops.
|
270
|
+
INSPECT_KEY = :__inspect_key__
|
271
|
+
private_constant :INSPECT_KEY
|
272
|
+
|
273
|
+
# @param maps [Array<#each_pair>] a list of maps which should be
|
274
|
+
# merged into the new {Weak::Map}
|
275
|
+
# @return [Weak::Map] a new {Weak::Map} object populated with the given
|
276
|
+
# objects, if any. With no argument, returns a new empty {Weak::Map}.
|
277
|
+
# @example
|
278
|
+
# hash = {foo: 0, bar: 1, baz: 2}
|
279
|
+
# Weak::Map[hash]
|
280
|
+
# # => #<Weak::Map {:foo=>0, :bar=>1, :baz=>2}>
|
281
|
+
|
282
|
+
def self.[](*maps)
|
283
|
+
Weak::Map.new.merge!(*maps)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns a new empty Weak::Map object.
|
287
|
+
#
|
288
|
+
# The initial default value and initial default proc for the new hash depend
|
289
|
+
# on which form above was used.
|
290
|
+
#
|
291
|
+
# If neither an `default_value` nor a block is given, initializes both the
|
292
|
+
# default value and the default proc to nil:
|
293
|
+
#
|
294
|
+
# map = Weak::Map.new
|
295
|
+
# map.default # => nil
|
296
|
+
# map.default_proc # => nil
|
297
|
+
#
|
298
|
+
# If a `default_value` is given but no block is given, initializes the
|
299
|
+
# default value to the given `default_value` and the default proc to nil:
|
300
|
+
#
|
301
|
+
# map = Hash.new(false)
|
302
|
+
# map.default # => false
|
303
|
+
# map.default_proc # => nil
|
304
|
+
#
|
305
|
+
# If a block is given but no `default_value`, stores the block as the
|
306
|
+
# default proc and sets the default value to nil:
|
307
|
+
#
|
308
|
+
# map = Hash.new { |map, key| "Default value for #{key}" }
|
309
|
+
# map.default # => nil
|
310
|
+
# map.default_proc.class # => Proc
|
311
|
+
# map[:nosuch] # => "Default value for nosuch"
|
312
|
+
#
|
313
|
+
# If both a block and a `default_value` are given, raises an `ArgumentError`
|
314
|
+
#
|
315
|
+
# @param default_value (see #default_value=)
|
316
|
+
def initialize(default_value = UNDEFINED, &default_proc)
|
317
|
+
clear
|
318
|
+
|
319
|
+
if UNDEFINED.equal?(default_value)
|
320
|
+
@default_value = nil
|
321
|
+
@default_proc = default_proc
|
322
|
+
elsif block_given?
|
323
|
+
raise ArgumentError, "wrong number of arguments (given 1, expected 0)"
|
324
|
+
else
|
325
|
+
@default_value = default_value
|
326
|
+
@default_proc = nil
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
alias_method :each, :each_pair
|
331
|
+
alias_method :has_key?, :include?
|
332
|
+
alias_method :key?, :include?
|
333
|
+
alias_method :member?, :include?
|
334
|
+
alias_method :length, :size
|
335
|
+
alias_method :store, :[]
|
336
|
+
|
337
|
+
# {Weak::Map} objects can't be frozen since this is not enforced by the
|
338
|
+
# underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
|
339
|
+
# this by not actually setting the `frozen?` flag and ignoring attempts to
|
340
|
+
# freeze us with just a warning.
|
341
|
+
#
|
342
|
+
# @param freeze [Bool, nil] ignored; we always behave as if this is false.
|
343
|
+
# If this is set to a truethy value, we emit a warning.
|
344
|
+
# @return [Weak::Set] a new `Weak::Map` object containing the same elements
|
345
|
+
# as `self`
|
346
|
+
def clone(freeze: false)
|
347
|
+
warn("Can't freeze #{self.class}") if freeze
|
348
|
+
|
349
|
+
super(freeze: false)
|
350
|
+
end
|
351
|
+
|
352
|
+
# This method does nothing as we always compare elements by their object
|
353
|
+
# identity.
|
354
|
+
#
|
355
|
+
# @return [self]
|
356
|
+
def compare_by_identity
|
357
|
+
self
|
358
|
+
end
|
359
|
+
|
360
|
+
# @return [true] always `true` since we always compare elements by their
|
361
|
+
# object identity
|
362
|
+
def compare_by_identity?
|
363
|
+
true
|
364
|
+
end
|
365
|
+
|
366
|
+
# Returns the default value for the given `key`. The returned value will be
|
367
|
+
# determined either by the default proc or by the default value. With no
|
368
|
+
# argument, returns the current default value (initially `nil`). If `key` is
|
369
|
+
# given, returns the default value for `key`, regardless of whether that key
|
370
|
+
# exists.
|
371
|
+
#
|
372
|
+
# @param key [Object] if given, we return the default value for this key
|
373
|
+
# @return [Object] the default value for `key` if given, or weak map's
|
374
|
+
# default value
|
375
|
+
def default(key = UNDEFINED)
|
376
|
+
if UNDEFINED.equal? key
|
377
|
+
@default_value
|
378
|
+
else
|
379
|
+
_default(key)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Sets the default value to `default_value` and clears the {#default_proc};
|
384
|
+
# returns `default_value`.
|
385
|
+
#
|
386
|
+
# @param default_value [Object] the new default value which will be returned
|
387
|
+
# when accessing a non-existing key
|
388
|
+
# @return [Object] the given `default_value`
|
389
|
+
def default=(default_value)
|
390
|
+
@default_proc = nil
|
391
|
+
@default_value = default_value
|
392
|
+
end
|
393
|
+
|
394
|
+
# @return [Proc, nil] the default proc for `self`
|
395
|
+
def default_proc
|
396
|
+
@default_proc
|
397
|
+
end
|
398
|
+
|
399
|
+
# Sets the default proc for self to `proc` and clears the {#default} value.
|
400
|
+
#
|
401
|
+
# @param proc [Proc, #to_proc nil] a `Proc` which can be called with two
|
402
|
+
# arguments: the map and the rquested non-exiting key. The proc is
|
403
|
+
# expected to return the default value for the key. Whe giving `nil`, the
|
404
|
+
# default proc is cleared.
|
405
|
+
# @return [Proc, nil] the new default proc
|
406
|
+
# @raise [TypeError] if the given `proc` can not be converted to a `Proc`.
|
407
|
+
def default_proc=(proc)
|
408
|
+
@default_value = nil
|
409
|
+
return @default_proc = nil if proc.nil?
|
410
|
+
|
411
|
+
if Proc === proc
|
412
|
+
default_proc = proc
|
413
|
+
elsif proc.respond_to?(:to_proc)
|
414
|
+
default_proc = proc.to_proc
|
415
|
+
unless Proc === default_proc
|
416
|
+
raise TypeError, "can't convert #{proc.class} to Proc " \
|
417
|
+
"(#{proc.class}#to_proc gives #{default_proc.class})"
|
418
|
+
end
|
419
|
+
else
|
420
|
+
raise TypeError, "no implicit conversion of #{proc.class} into Proc"
|
421
|
+
end
|
422
|
+
|
423
|
+
if default_proc.lambda?
|
424
|
+
arity = default_proc.arity
|
425
|
+
if arity != 2 && (arity >= 0 || arity < -3)
|
426
|
+
arity = -arity - 1 if arity < 0
|
427
|
+
raise TypeError, "default_proc takes two arguments (2 for #{arity})"
|
428
|
+
end
|
429
|
+
end
|
430
|
+
@default_proc = default_proc
|
431
|
+
|
432
|
+
proc
|
433
|
+
end
|
434
|
+
|
435
|
+
# @return [Boolean] `true` if `self` contains no elements
|
436
|
+
def empty?
|
437
|
+
size == 0
|
438
|
+
end
|
439
|
+
|
440
|
+
# {Weak::Set} objects can't be frozen since this is not enforced by the
|
441
|
+
# underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
|
442
|
+
# this by not actually setting the `frozen?` flag and ignoring attempts to
|
443
|
+
# freeze us with just a warning.
|
444
|
+
#
|
445
|
+
# @return [self]
|
446
|
+
def freeze
|
447
|
+
warn("Can't freeze #{self.class}")
|
448
|
+
self
|
449
|
+
end
|
450
|
+
|
451
|
+
# @param value [Object] a value to check
|
452
|
+
# @return [Bool] `true` if `value` is a value in `self`, `false` otherwise
|
453
|
+
#
|
454
|
+
# @!macro _note_object_equality
|
455
|
+
def has_value?(value)
|
456
|
+
id = value.__id__
|
457
|
+
each_value.any? { |v| v.__id__ == id }
|
458
|
+
end
|
459
|
+
alias_method :value?, :has_value?
|
460
|
+
|
461
|
+
# @return [String] a string containing a human-readable representation of
|
462
|
+
# the weak set, e.g.,
|
463
|
+
# `"#<Weak::Map {key1 => value1, key2 => value2, ...}>"`
|
464
|
+
def inspect
|
465
|
+
object_ids = (Thread.current[INSPECT_KEY] ||= [])
|
466
|
+
return "#<#{self.class} {...}>" if object_ids.include?(object_id)
|
467
|
+
|
468
|
+
object_ids << object_id
|
469
|
+
begin
|
470
|
+
elements = to_a.sort_by! { |k, _v| k.__id__ }.to_h.inspect[1..-2]
|
471
|
+
"#<#{self.class} {#{elements}}>"
|
472
|
+
ensure
|
473
|
+
object_ids.pop
|
474
|
+
end
|
475
|
+
end
|
476
|
+
alias_method :to_s, :inspect
|
477
|
+
|
478
|
+
# Returns the new {Weak::Map} formed by merging each of `other_maps` into a
|
479
|
+
# copy of `self`.
|
480
|
+
#
|
481
|
+
# Each argument in other_maos must be respond to `each_pair`, e.g. a
|
482
|
+
# {Weak::Map} or a `Hash`.
|
483
|
+
#
|
484
|
+
# With arguments and no block:
|
485
|
+
#
|
486
|
+
# - Returns a new {Weak::Map}, after the given maps are merged into a copy
|
487
|
+
# of `self`.
|
488
|
+
# - The given hashes are merged left to right.
|
489
|
+
# - Each duplicate-key entry’s value overwrites the previous value.
|
490
|
+
#
|
491
|
+
# Example:
|
492
|
+
#
|
493
|
+
# map = Weak::Map.new
|
494
|
+
# map[:foo] = 0
|
495
|
+
# map[:bar] = 1
|
496
|
+
#
|
497
|
+
# h1 = {baz: 3, bar: 4}
|
498
|
+
# h2 = {bam: 5, baz: 6}
|
499
|
+
# map.merge(h1, h2)
|
500
|
+
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
|
501
|
+
#
|
502
|
+
# With arguments and a block:
|
503
|
+
#
|
504
|
+
# - Returns `self`, after the given maps are merged.
|
505
|
+
# - The given hashes are merged left to right.
|
506
|
+
# - For each duplicate key:
|
507
|
+
# - Calls the block with the key and the old and new values.
|
508
|
+
# - The block’s return value becomes the new value for the entry.
|
509
|
+
# - The block should only return values which are otherwise strongly
|
510
|
+
# referenced to ensure that the value is not immediately
|
511
|
+
# garbage-collected.
|
512
|
+
#
|
513
|
+
# Example:
|
514
|
+
#
|
515
|
+
# map = Weak::Map.new
|
516
|
+
# map[:foo] = 0
|
517
|
+
# map[:bar] = 1
|
518
|
+
#
|
519
|
+
# h1 = {baz: 3, bar: 4}
|
520
|
+
# h2 = {bam: 5, baz: 6}
|
521
|
+
# map.merge(h1, h2) { |key, old_value, new_value| old_value + new_value }
|
522
|
+
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
|
523
|
+
#
|
524
|
+
# With no arguments:
|
525
|
+
#
|
526
|
+
# - Returns a copy of `self`.
|
527
|
+
# - The block, if given, is ignored.
|
528
|
+
#
|
529
|
+
# @param other_maps [Array<#each_pair>] a list of maps which should be
|
530
|
+
# merged into a copy of `self`
|
531
|
+
# @yield [key, old_value, new_value] If `self` already contains a value for
|
532
|
+
# a key, we yield the key, the old value from `self` and the new value
|
533
|
+
# from the given map and use the value returned from the block as the new
|
534
|
+
# value to be merged.
|
535
|
+
# @yieldparam key [Object] the conflicting key
|
536
|
+
# @yieldparam old_value [Object] the existing value from `self`
|
537
|
+
# @yieldparam old_value [Object] the new value from one of the given
|
538
|
+
# `other_maps`
|
539
|
+
# @return [Weak::Map] a new weak map containing the merged pairs
|
540
|
+
#
|
541
|
+
# @!macro _note_object_equality
|
542
|
+
def merge(*other_maps, &block)
|
543
|
+
dup.merge!(*other_maps, &block)
|
544
|
+
end
|
545
|
+
|
546
|
+
# @!visibility private
|
547
|
+
def pretty_print(pp)
|
548
|
+
pp.group(1, "#<#{self.class}", ">") do
|
549
|
+
pp.breakable
|
550
|
+
pp.pp to_a.sort_by! { |k, _v| k.__id__ }.to_h
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
# Merges each of `other_maps` into `self`; returns `self`.
|
555
|
+
#
|
556
|
+
# Each argument in other_maos must be respond to `each_pair`, e.g. a
|
557
|
+
# {Weak::Map} or a `Hash`.
|
558
|
+
#
|
559
|
+
# With arguments and no block:
|
560
|
+
#
|
561
|
+
# - Returns self, after the given maps are merged into it.
|
562
|
+
# - The given hashes are merged left to right.
|
563
|
+
# - Each duplicate-key entry’s value overwrites the previous value.
|
564
|
+
#
|
565
|
+
# Example:
|
566
|
+
#
|
567
|
+
# map = Weak::Map.new
|
568
|
+
# map[:foo] = 0
|
569
|
+
# map[:bar] = 1
|
570
|
+
#
|
571
|
+
# h1 = {baz: 3, bar: 4}
|
572
|
+
# h2 = {bam: 5, baz: 6}
|
573
|
+
# map.update(h1, h2)
|
574
|
+
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
|
575
|
+
#
|
576
|
+
# With arguments and a block:
|
577
|
+
#
|
578
|
+
# - Returns `self`, after the given maps are merged.
|
579
|
+
# - The given hashes are merged left to right.
|
580
|
+
# - For each duplicate key:
|
581
|
+
# - Calls the block with the key and the old and new values.
|
582
|
+
# - The block’s return value becomes the new value for the entry.
|
583
|
+
# - The block should only return values which are otherwise strongly
|
584
|
+
# referenced to ensure that the value is not immediately
|
585
|
+
# garbage-collected.
|
586
|
+
#
|
587
|
+
# Example:
|
588
|
+
#
|
589
|
+
# map = Weak::Map.new
|
590
|
+
# map[:foo] = 0
|
591
|
+
# map[:bar] = 1
|
592
|
+
#
|
593
|
+
# h1 = {baz: 3, bar: 4}
|
594
|
+
# h2 = {bam: 5, baz: 6}
|
595
|
+
# map.update(h1, h2) { |key, old_value, new_value| old_value + new_value }
|
596
|
+
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
|
597
|
+
#
|
598
|
+
# With no arguments:
|
599
|
+
#
|
600
|
+
# - Returns `self`.
|
601
|
+
# - The block, if given, is ignored.
|
602
|
+
#
|
603
|
+
# @param other_maps [Array<#each_pair>] a list of maps which should be
|
604
|
+
# merged into `self`
|
605
|
+
# @yield [key, old_value, new_value] If `self` already contains a value for
|
606
|
+
# a key, we yield the key, the old value from `self` and the new value
|
607
|
+
# from the given map and use the value returned from the block as the new
|
608
|
+
# value to be merged.
|
609
|
+
# @yieldparam key [Object] the conflicting key
|
610
|
+
# @yieldparam old_value [Object] the existing value from `self`
|
611
|
+
# @yieldparam old_value [Object] the new value from one of the given
|
612
|
+
# `other_maps`
|
613
|
+
# @return [self]
|
614
|
+
#
|
615
|
+
# @!macro _note_object_equality
|
616
|
+
def update(*other_maps)
|
617
|
+
if block_given?
|
618
|
+
missing = Object.new
|
619
|
+
|
620
|
+
other_maps.each do |map|
|
621
|
+
map.each_pair do |key, value|
|
622
|
+
old_value = fetch(key, missing)
|
623
|
+
value = yield(key, old_value, value) unless missing == old_value
|
624
|
+
self[key] = value
|
625
|
+
end
|
626
|
+
end
|
627
|
+
else
|
628
|
+
other_maps.each do |map|
|
629
|
+
map.each_pair do |key, value|
|
630
|
+
self[key] = value
|
631
|
+
end
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
self
|
636
|
+
end
|
637
|
+
alias_method :merge!, :update
|
638
|
+
|
639
|
+
# @return [Array] a new `Array` of 2-element `Array` objects; each nested
|
640
|
+
# `Array` contains a key-value pair from self
|
641
|
+
def to_a
|
642
|
+
to_h.to_a
|
643
|
+
end
|
644
|
+
|
645
|
+
# @yield [key, value] When a block is given, returns a new Hash object whose
|
646
|
+
# content is based on the block; the block should return a 2-element Array
|
647
|
+
# object specifying the key-value pair to be included in the returned
|
648
|
+
# Hash.
|
649
|
+
# @yieldparam key [Object] the key of the current key-value pair
|
650
|
+
# @yieldparam value [Object] the value of the current key-value pair
|
651
|
+
# @return [Hash] a new `Hash` which considers object identity for keys which
|
652
|
+
# contains the key-value pairs in `self`.
|
653
|
+
def to_h(&block)
|
654
|
+
hash = {}.compare_by_identity
|
655
|
+
if block_given?
|
656
|
+
each do |key, value|
|
657
|
+
map = yield(key, value)
|
658
|
+
ary = Array.try_convert(map)
|
659
|
+
unless ary
|
660
|
+
raise TypeError, "wrong element type #{map.class} (expected array)"
|
661
|
+
end
|
662
|
+
unless ary.size == 2
|
663
|
+
raise ArgumentError, "element has wrong array length " \
|
664
|
+
"(expected 2, was #{ary.size})"
|
665
|
+
end
|
666
|
+
|
667
|
+
hash[ary[0]] = ary[1]
|
668
|
+
end
|
669
|
+
else
|
670
|
+
each do |key, value|
|
671
|
+
hash[key] = value
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
hash
|
676
|
+
end
|
677
|
+
|
678
|
+
private
|
679
|
+
|
680
|
+
# Callback method which is called on the new object during `dup` or `clone`
|
681
|
+
def initialize_copy(orig)
|
682
|
+
initialize
|
683
|
+
merge!(orig)
|
684
|
+
if orig.default_proc
|
685
|
+
self.default_proc = orig.default_proc
|
686
|
+
else
|
687
|
+
self.default = orig.default
|
688
|
+
end
|
689
|
+
|
690
|
+
self
|
691
|
+
end
|
692
|
+
|
693
|
+
def _default(key)
|
694
|
+
@default_proc ? default_proc.call(self, key) : @default_value
|
695
|
+
end
|
696
|
+
|
697
|
+
def _fetch_default(key, default = UNDEFINED)
|
698
|
+
have_default = !UNDEFINED.equal?(default)
|
699
|
+
|
700
|
+
if block_given?
|
701
|
+
warn("warning: block supersedes default value argument") if have_default
|
702
|
+
yield(key)
|
703
|
+
elsif have_default
|
704
|
+
default
|
705
|
+
else
|
706
|
+
raise KeyError.new(
|
707
|
+
"key not found: #{key.inspect}",
|
708
|
+
receiver: self,
|
709
|
+
key: key
|
710
|
+
)
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|
714
|
+
end
|