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.
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