Class: Weak::Map
- Inherits:
-
Object
- Object
- Weak::Map
- Includes:
- Enumerable, [ Weak[ Weak::Map[ Weak::Map::WeakKeysWithDelete, Weak::Map::WeakKeys, Weak::Map::StrongKeys, Weak::Map::StrongSecondaryKeys ].find(&:usable?)
- Defined in:
- lib/weak/map.rb,
lib/weak/map/deletable.rb,
lib/weak/map/weak_keys.rb,
lib/weak/map/strong_keys.rb,
lib/weak/map/abstract_strong_keys.rb,
lib/weak/map/strong_secondary_keys.rb,
lib/weak/map/weak_keys_with_delete.rb
Overview
Weak::Map behaves similar to a Hash or an ObjectSpace::WeakMap in Ruby (aka. MRI, aka. YARV). Both keys and values are weakly referenceed, allowing either of them to be independently garbage collected. If either the key or the value of a pair is garbage collected, the entire pair will be removed from the Weak::Map.
Map uses ObjectSpace::WeakMap as storage, so you must note the following points:
-
Equality of both keys and values is determined strictly by their object
identity instead of `Object#eql?` or `Object#hash` as the `Hash` class does by default. -
Keys and values can be freely changed without affecting the map.
-
Keys and values can be freely garbage collected by Ruby. A key-value pair will be removed from the map automatically if theoer the key or the value is garbage collected.
-
The order of key-value pairs in the map is non-deterministic. Insertion order is not preserved.
Note that Map is not inherently thread-safe. When accessing a Map from multiple threads or fibers, you MUST use a mutex or another locking mechanism.
## Implementation Details
The various Ruby implementations and versions show quite diverse behavior in their respective ObjectSpace::WeakMap implementations. To provide a unified behavior on all implementations, we use different storage strategies:
-
Ruby (aka. MRI, aka. YARV) >= 3.3 has an
ObjectSpace::WeakMapwith weak keys and weak values and the ability to delete elements from it. This allows a straight-forward implementation in WeakKeysWithDelete. -
Ruby (aka. MRI, aka. YARV) < 3.3 has an
ObjectSpace::WeakMapwith weak keys and weak values but does not allow to directly delete entries. We emulate this with special garbage-collectible values in WeakKeys. -
JRuby >= 9.4.6.0 and TruffleRuby >= 22 have an
ObjectSpace::WeakMapwith strong keys and weak values. To allow both keys and values to be garbage collected, we can’t use the actual object as a key in a singleObjectSpace::WeakMap. Instead, we use a sepateWeakMapfor keys and values which in turn use the key’sobject_idas a key. As theseObjectSpace::WeakMapobjects also do not allow to delete entries, we emulate deletion with special garbage-collectible values as above. This is implemented in StrongKeys. -
JRuby < 9.4.6.0 has a similar
ObjectSpace::WeakMapas newer JRuby versions with strong keys and weak values. However generally in JRuby, Integer values (including object_ids) can have multiple different object representations in memory and are not necessarily equal to each other when used as keys in anObjectSpace::WeakMap. As a workaround we use an indirect implementation with a secondary lookup table for the map keys in for both stored keys and values StrongSecondaryKeys.
The required strategy is selected automatically based in the running Ruby. The external behavior is the same for all implementations.
Defined Under Namespace
Modules: AbstractStrongKeys, StrongKeys, StrongSecondaryKeys, WeakKeys, WeakKeysWithDelete
Constant Summary collapse
- STRATEGY =
We try to find the best implementation strategy based on the current Ruby engine and version. The chosen
STRATEGYis included into the Weak::Map class. [ Weak::Map::WeakKeysWithDelete, Weak::Map::WeakKeys, Weak::Map::StrongKeys, Weak::Map::StrongSecondaryKeys ].find(&:usable?)
Class Method Summary collapse
-
.[](*maps) ⇒ Weak::Map
A new Map object populated with the given objects, if any.
Instance Method Summary collapse
-
#[](key) ⇒ Object
(also: #store)
The value associated with the given
key, if found. -
#[]=(key, value) ⇒ Object
Associates the given
valuewith the givenkey; returnsvalue. -
#clear ⇒ self
Removes all elements and returns
self. -
#clone(freeze: false) ⇒ Weak::Set
Map objects can’t be frozen since this is not enforced by the underlying
ObjectSpace::WeakMapimplementation. -
#compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
-
#compare_by_identity? ⇒ true
Always
truesince we always compare elements by their object identity. -
#default(key = UNDEFINED) ⇒ Object
Returns the default value for the given
key. -
#default=(default_value) ⇒ Object
Sets the default value to
default_valueand clears the #default_proc; returnsdefault_value. -
#default_proc ⇒ Proc?
The default proc for
self. -
#default_proc=(proc) ⇒ Proc?
Sets the default proc for self to
procand clears the #default value. -
#delete(key = UNDEFINED) {|key| ... } ⇒ Object?
Deletes the key-value pair and returns the value from
selfwhose key is equal tokey. -
#each_key {|key| ... } ⇒ self, Enumerator
Calls the given block once for each live key in
self, passing the key as a parameter. -
#each_pair {|key, value| ... } ⇒ self, Enumerator
(also: #each)
Calls the given block once for each live key in
self, passing the key and value as parameters. -
#each_value {|value| ... } ⇒ self, Enumerator
Calls the given block once for each live key
self, passing the live value associated with the key as a parameter. -
#empty? ⇒ Boolean
trueifselfcontains no elements. -
#fetch(key, default = UNDEFINED) {|key| ... } ⇒ Object
Returns a value from the hash for the given
key. -
#freeze ⇒ self
Set objects can’t be frozen since this is not enforced by the underlying
ObjectSpace::WeakMapimplementation. -
#has_value?(value) ⇒ Bool
(also: #value?)
trueifvalueis a value inself,falseotherwise. -
#include?(key) ⇒ Bool
(also: #has_key?, #key?, #member?)
trueif the given key is included inselfand has an associated live value,falseotherwise. -
#initialize(default_value = UNDEFINED, &default_proc) ⇒ Map
constructor
Returns a new empty Weak::Map object.
-
#inspect ⇒ String
(also: #to_s)
A string containing a human-readable representation of the weak set, e.g., ‘“#<Weak::Map => value1, key2 => value2, …>”`.
-
#keys ⇒ Array
An
Arraycontaining all keys of the map for which we have a valid value. -
#merge(*other_maps) {|key, old_value, new_value| ... } ⇒ Weak::Map
Returns the new Map formed by merging each of
other_mapsinto a copy ofself. -
#prune ⇒ self
Cleanup data structures from the map to remove data associated with deleted or garbage collected keys and/or values.
-
#size ⇒ Integer
(also: #length)
The number of live key-value pairs in
self. -
#to_a ⇒ Array
A new
Arrayof 2-elementArrayobjects; each nestedArraycontains a key-value pair from self. -
#to_h {|key, value| ... } ⇒ Hash
A new
Hashwhich considers object identity for keys which contains the key-value pairs inself. -
#update(*other_maps) {|key, old_value, new_value| ... } ⇒ self
(also: #merge!)
Merges each of
other_mapsintoself; returnsself. -
#values ⇒ Array
An
Arraycontaining all values of the map for which we have a valid key.
Constructor Details
#initialize(default_value = UNDEFINED, &default_proc) ⇒ Map
Returns a new empty Weak::Map object.
The initial default value and initial default proc for the new hash depend on which form above was used.
If neither an default_value nor a block is given, initializes both the default value and the default proc to nil:
map = Weak::Map.new
map.default # => nil
map.default_proc # => nil
If a default_value is given but no block is given, initializes the default value to the given default_value and the default proc to nil:
map = Hash.new(false)
map.default # => false
map.default_proc # => nil
If a block is given but no default_value, stores the block as the default proc and sets the default value to nil:
map = Hash.new { |map, key| "Default value for #{key}" }
map.default # => nil
map.default_proc.class # => Proc
map[:nosuch] # => "Default value for nosuch"
If both a block and a default_value are given, raises an ArgumentError
316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/weak/map.rb', line 316 def initialize(default_value = UNDEFINED, &default_proc) clear if UNDEFINED.equal?(default_value) @default_value = nil @default_proc = default_proc elsif block_given? raise ArgumentError, "wrong number of arguments (given 1, expected 0)" else @default_value = default_value @default_proc = nil end end |
Class Method Details
Instance Method Details
#[](key) ⇒ Object Also known as: store
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Returns the value associated with the given key, if found. If key is not found, returns the default value, i.e. the value returned by the default proc (if defined) or the default value (which is initially nil.).
|
|
# File 'lib/weak/map.rb', line 227
|
#[]=(key, value) ⇒ Object
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Associates the given value with the given key; returns value. If the given key exists, replaces its value with the given value.
|
|
# File 'lib/weak/map.rb', line 230
|
#clear ⇒ self
Removes all elements and returns self
|
|
# File 'lib/weak/map.rb', line 233
|
#clone(freeze: false) ⇒ Weak::Set
Weak::Map objects can’t be frozen since this is not enforced by the underlying ObjectSpace::WeakMap implementation. Thus, we try to signal this by not actually setting the frozen? flag and ignoring attempts to freeze us with just a warning.
346 347 348 349 350 |
# File 'lib/weak/map.rb', line 346 def clone(freeze: false) warn("Can't freeze #{self.class}") if freeze super(freeze: false) end |
#compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
356 357 358 |
# File 'lib/weak/map.rb', line 356 def compare_by_identity self end |
#compare_by_identity? ⇒ true
362 363 364 |
# File 'lib/weak/map.rb', line 362 def compare_by_identity? true end |
#default(key = UNDEFINED) ⇒ Object
Returns the default value for the given key. The returned value will be determined either by the default proc or by the default value. With no argument, returns the current default value (initially nil). If key is given, returns the default value for key, regardless of whether that key exists.
375 376 377 378 379 380 381 |
# File 'lib/weak/map.rb', line 375 def default(key = UNDEFINED) if UNDEFINED.equal? key @default_value else _default(key) end end |
#default=(default_value) ⇒ Object
Sets the default value to default_value and clears the #default_proc; returns default_value.
389 390 391 392 |
# File 'lib/weak/map.rb', line 389 def default=(default_value) @default_proc = nil @default_value = default_value end |
#default_proc ⇒ Proc?
395 396 397 |
# File 'lib/weak/map.rb', line 395 def default_proc @default_proc end |
#default_proc=(proc) ⇒ Proc?
Sets the default proc for self to proc and clears the #default value.
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'lib/weak/map.rb', line 407 def default_proc=(proc) @default_value = nil return @default_proc = nil if proc.nil? if Proc === proc default_proc = proc elsif proc.respond_to?(:to_proc) default_proc = proc.to_proc unless Proc === default_proc raise TypeError, "can't convert #{proc.class} to Proc " \ "(#{proc.class}#to_proc gives #{default_proc.class})" end else raise TypeError, "no implicit conversion of #{proc.class} into Proc" end if default_proc.lambda? arity = default_proc.arity if arity != 2 && (arity >= 0 || arity < -3) arity = -arity - 1 if arity < 0 raise TypeError, "default_proc takes two arguments (2 for #{arity})" end end @default_proc = default_proc proc end |
#delete(key = UNDEFINED) {|key| ... } ⇒ Object?
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Deletes the key-value pair and returns the value from self whose key is equal to key. If the key is not found, it returns nil. If the optional block is given and the key is not found, pass in the key and return the result of the block.
|
|
# File 'lib/weak/map.rb', line 236
|
#each_key {|key| ... } ⇒ self, Enumerator
Calls the given block once for each live key in self, passing the key as a parameter. Returns the weak map itself.
If no block is given, an Enumerator is returned instead.
|
|
# File 'lib/weak/map.rb', line 242
|
#each_pair {|key, value| ... } ⇒ self, Enumerator Also known as: each
Calls the given block once for each live key in self, passing the key and value as parameters. Returns the weak map itself.
If no block is given, an Enumerator is returned instead.
|
|
# File 'lib/weak/map.rb', line 239
|
#each_value {|value| ... } ⇒ self, Enumerator
Calls the given block once for each live key self, passing the live value associated with the key as a parameter. Returns the weak map itself.
If no block is given, an Enumerator is returned instead.
|
|
# File 'lib/weak/map.rb', line 245
|
#empty? ⇒ Boolean
436 437 438 |
# File 'lib/weak/map.rb', line 436 def empty? size == 0 end |
#fetch(key, default = UNDEFINED) {|key| ... } ⇒ Object
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Returns a value from the hash for the given key. If the key can’t be found, there are several options: With no other arguments, it will raise a KeyError exception; if default is given, then that value will be returned; if the optional code block is specified, then it will be called and its result returned.
|
|
# File 'lib/weak/map.rb', line 248
|
#freeze ⇒ self
Set objects can’t be frozen since this is not enforced by the underlying ObjectSpace::WeakMap implementation. Thus, we try to signal this by not actually setting the frozen? flag and ignoring attempts to freeze us with just a warning.
446 447 448 449 |
# File 'lib/weak/map.rb', line 446 def freeze warn("Can't freeze #{self.class}") self end |
#has_value?(value) ⇒ Bool Also known as: value?
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Returns true if value is a value in self, false otherwise.
455 456 457 458 |
# File 'lib/weak/map.rb', line 455 def has_value?(value) id = value.__id__ each_value.any? { |v| v.__id__ == id } end |
#include?(key) ⇒ Bool Also known as: has_key?, key?, member?
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Returns true if the given key is included in self and has an associated live value, false otherwise.
|
|
# File 'lib/weak/map.rb', line 251
|
#inspect ⇒ String Also known as: to_s
464 465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/weak/map.rb', line 464 def inspect object_ids = (Thread.current[INSPECT_KEY] ||= []) return "#<#{self.class} {...}>" if object_ids.include?(object_id) object_ids << object_id begin elements = to_a.sort_by! { |k, _v| k.__id__ }.to_h.inspect[1..-2] "#<#{self.class} {#{elements}}>" ensure object_ids.pop end end |
#keys ⇒ Array
In contrast to a Hash, ‘Weak::Map`s do not necessarily retain insertion order.
Returns an Array containing all keys of the map for which we have a valid value. Keys with garbage-collected values are excluded.
|
|
# File 'lib/weak/map.rb', line 254
|
#merge(*other_maps) {|key, old_value, new_value| ... } ⇒ Weak::Map
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Returns the new Weak::Map formed by merging each of other_maps into a copy of self.
Each argument in other_maos must be respond to each_pair, e.g. a Weak::Map or a Hash.
With arguments and no block:
- Returns a new {Weak::Map}, after the given maps are merged into a copy
of `self`.
- The given hashes are merged left to right.
- Each duplicate-key entry
Example:
map = Weak::Map.new
map[:foo] = 0
map[:bar] = 1
h1 = {baz: 3, bar: 4}
h2 = {bam: 5, baz: 6}
map.merge(h1, h2)
# => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
With arguments and a block:
- Returns `self`, after the given maps are merged.
- The given hashes are merged left to right.
- For each duplicate key:
- Calls the block with the key and the old and new values.
- The block
Example:
map = Weak::Map.new
map[:foo] = 0
map[:bar] = 1
h1 = {baz: 3, bar: 4}
h2 = {bam: 5, baz: 6}
map.merge(h1, h2) { |key, old_value, new_value| old_value + new_value }
# => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
With no arguments:
- Returns a copy of `self`.
- The block, if given, is ignored.
542 543 544 |
# File 'lib/weak/map.rb', line 542 def merge(*other_maps, &block) dup.merge!(*other_maps, &block) end |
#prune ⇒ self
Cleanup data structures from the map to remove data associated with deleted or garbage collected keys and/or values. This method may be called automatically for some Weak::Map operations.
|
|
# File 'lib/weak/map.rb', line 257
|
#size ⇒ Integer Also known as: length
|
|
# File 'lib/weak/map.rb', line 260
|
#to_a ⇒ Array
641 642 643 |
# File 'lib/weak/map.rb', line 641 def to_a to_h.to_a end |
#to_h {|key, value| ... } ⇒ Hash
Returns a new Hash which considers object identity for keys which contains the key-value pairs in self.
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 |
# File 'lib/weak/map.rb', line 653 def to_h(&block) hash = {}.compare_by_identity if block_given? each do |key, value| map = yield(key, value) ary = Array.try_convert(map) unless ary raise TypeError, "wrong element type #{map.class} (expected array)" end unless ary.size == 2 raise ArgumentError, "element has wrong array length " \ "(expected 2, was #{ary.size})" end hash[ary[0]] = ary[1] end else each do |key, value| hash[key] = value end end hash end |
#update(*other_maps) {|key, old_value, new_value| ... } ⇒ self Also known as: merge!
Weak::Map does not test member equality with ‘==` or eql?. Instead, it always checks strict object equality, so that, e.g., different String keys are not considered equal, even if they may contain the same content.
Merges each of other_maps into self; returns self.
Each argument in other_maos must be respond to each_pair, e.g. a Weak::Map or a Hash.
With arguments and no block:
- Returns self, after the given maps are merged into it.
- The given hashes are merged left to right.
- Each duplicate-key entry
Example:
map = Weak::Map.new
map[:foo] = 0
map[:bar] = 1
h1 = {baz: 3, bar: 4}
h2 = {bam: 5, baz: 6}
map.update(h1, h2)
# => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
With arguments and a block:
- Returns `self`, after the given maps are merged.
- The given hashes are merged left to right.
- For each duplicate key:
- Calls the block with the key and the old and new values.
- The block
Example:
map = Weak::Map.new
map[:foo] = 0
map[:bar] = 1
h1 = {baz: 3, bar: 4}
h2 = {bam: 5, baz: 6}
map.update(h1, h2) { |key, old_value, new_value| old_value + new_value }
# => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
With no arguments:
- Returns `self`.
- The block, if given, is ignored.
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 |
# File 'lib/weak/map.rb', line 616 def update(*other_maps) if block_given? missing = Object.new other_maps.each do |map| map.each_pair do |key, value| old_value = fetch(key, missing) value = yield(key, old_value, value) unless missing == old_value self[key] = value end end else other_maps.each do |map| map.each_pair do |key, value| self[key] = value end end end self end |
#values ⇒ Array
In contrast to a Hash, ‘Weak::Map`s do not necessarily retain insertion order.
Returns an Array containing all values of the map for which we have a valid key. Values with garbage-collected keys are excluded.
|
|
# File 'lib/weak/map.rb', line 263
|