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?orObject#hashas theHashclass 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. You can also use Cache as a thread-safe alternative.
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
The value associated with the given
key, if found. -
#[]=(key, value) ⇒ Object
(also: #store)
Associates the given
valuewith the givenkey; returnsvalue. -
#clear ⇒ self
Removes all elements and returns
self. -
#clone(freeze: false) ⇒ Weak::Map
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. -
#delete_if {|key, value| ... } ⇒ Enumerator, self
Deletes every key-value pair from
selffor which the given block evaluates to a truthy value. -
#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
Map 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 {key1 => value1, key2 => value2, ...}>". -
#keep_if {|key, value| ... } ⇒ Enumerator, self
Deletes every key-value pair from
selffor which the given block evaluates to a falsey value. -
#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.
-
#reject! {|key, value| ... } ⇒ Enumerator, ...
Deletes every key-value pair from
selffor which the given block evaluates to a truethy value. -
#replace(map) ⇒ self
Replaces the contents of
selfwith the contents of the given Hash-like object and returnsself. -
#select! {|key, value| ... } ⇒ Enumerator, ...
(also: #filter!)
Deletes every key-value pair from
selffor which the given block evaluates to a falsey value. -
#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. -
#values_at(*keys) ⇒ Array
Returns a new
Arraycontaining values for the given keys:.
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 a 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
317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/weak/map.rb', line 317 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
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 229
|
#[]=(key, value) ⇒ 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.
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 232
|
#clear ⇒ self
Removes all elements and returns self
|
|
# File 'lib/weak/map.rb', line 235
|
#clone(freeze: false) ⇒ Weak::Map
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.
347 348 349 350 351 |
# File 'lib/weak/map.rb', line 347 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.
357 358 359 |
# File 'lib/weak/map.rb', line 357 def compare_by_identity self end |
#compare_by_identity? ⇒ true
Returns always true since we always compare elements by their
object identity.
363 364 365 |
# File 'lib/weak/map.rb', line 363 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.
376 377 378 379 380 381 382 |
# File 'lib/weak/map.rb', line 376 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.
390 391 392 393 |
# File 'lib/weak/map.rb', line 390 def default=(default_value) @default_proc = nil @default_value = default_value end |
#default_proc ⇒ Proc?
Returns the default proc for self.
396 397 398 |
# File 'lib/weak/map.rb', line 396 def default_proc @default_proc end |
#default_proc=(proc) ⇒ Proc?
Sets the default proc for self to proc and clears the #default value.
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 434 |
# File 'lib/weak/map.rb', line 408 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 238
|
#delete_if {|key, value| ... } ⇒ Enumerator, self
Deletes every key-value pair from self for which the given block
evaluates to a truthy value.
If no block is given, an Enumerator is returned instead.
447 448 449 450 451 452 453 454 |
# File 'lib/weak/map.rb', line 447 def delete_if(&block) return enum_for(__method__) { size } unless block_given? each do |key, value| delete(key) if yield(key, value) end self end |
#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 244
|
#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 241
|
#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 247
|
#empty? ⇒ Boolean
Returns true if self contains no elements.
457 458 459 |
# File 'lib/weak/map.rb', line 457 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 250
|
#freeze ⇒ self
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.
467 468 469 470 |
# File 'lib/weak/map.rb', line 467 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.
476 477 478 479 |
# File 'lib/weak/map.rb', line 476 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 253
|
#inspect ⇒ String Also known as: to_s
Returns a string containing a human-readable representation of
the weak set, e.g.,
"#<Weak::Map {key1 => value1, key2 => value2, ...}>".
485 486 487 |
# File 'lib/weak/map.rb', line 485 def inspect "#<#{self.class} #{_inspect}>" end |
#keep_if {|key, value| ... } ⇒ Enumerator, self
Deletes every key-value pair from self for which the given block
evaluates to a falsey value.
If no block is given, an Enumerator is returned instead.
517 518 519 520 521 522 523 524 |
# File 'lib/weak/map.rb', line 517 def keep_if(&block) return enum_for(__method__) { size } unless block_given? each do |key, value| delete(key) unless yield(key, value) end self end |
#keys ⇒ Array
In contrast to a Hash, Weak::Maps 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 256
|
#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_maps must be either a Weak::Map, a Hash object
or must be transformable to a Hash by calling each_hash on it.
With arguments and no block:
- Returns a new Weak::Map, after the given maps are merged into a copy
of
self. - The given maps are merged left to right.
- Each duplicate-key entry’s value overwrites the previous value.
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 maps are merged left to right.
- For each duplicate key:
- Calls the block with the key and the old and new values.
- The block’s return value becomes the new value for the entry.
- The block should only return values which are otherwise strongly referenced to ensure that the value is not immediately garbage-collected.
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.
590 591 592 |
# File 'lib/weak/map.rb', line 590 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 259
|
#reject! {|key, value| ... } ⇒ Enumerator, ...
Deletes every key-value pair from self for which the given block
evaluates to a truethy value.
Equivalent to #delete_if, but returns nil if no changes were made.
If no block is given, an Enumerator is returned instead.
620 621 622 623 624 625 626 627 628 629 630 631 632 |
# File 'lib/weak/map.rb', line 620 def reject!(&block) return enum_for(__method__) { size } unless block_given? deleted_anything = false each do |key, value| next unless yield(key, value) delete(key) deleted_anything = true end self if deleted_anything end |
#replace(map) ⇒ self
Replaces the contents of self with the contents of the given Hash-like
object and returns self.
If the given map defines a #default value or #default_proc, this
will also replace the respective seting in self.
646 647 648 |
# File 'lib/weak/map.rb', line 646 def replace(map) initialize_copy(_implicit(map)) end |
#select! {|key, value| ... } ⇒ Enumerator, ... Also known as: filter!
Deletes every key-value pair from self for which the given block
evaluates to a falsey value.
Equivalent to #keep_if, but returns nil if no changes were made.
If no block is given, an Enumerator is returned instead.
664 665 666 667 668 669 670 671 672 673 674 675 676 |
# File 'lib/weak/map.rb', line 664 def select!(&block) return enum_for(__method__) { size } unless block_given? deleted_anything = false each do |key, value| next if yield(key, value) delete(key) deleted_anything = true end self if deleted_anything end |
#size ⇒ Integer Also known as: length
Returns the number of live key-value pairs in self.
|
|
# File 'lib/weak/map.rb', line 262
|
#to_a ⇒ Array
Returns a new Array of 2-element Array objects; each nested
Array contains a key-value pair from self.
766 767 768 |
# File 'lib/weak/map.rb', line 766 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.
778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 |
# File 'lib/weak/map.rb', line 778 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_maps must be either a Weak::Map, a Hash object
or must be transformable to a Hash by calling each_hash on it.
With arguments and no block:
- Returns self, after the given maps are merged into it.
- The given maps are merged left to right.
- Each duplicate-key entry’s value overwrites the previous value.
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 maps are merged left to right.
- For each duplicate key:
- Calls the block with the key and the old and new values.
- The block’s return value becomes the new value for the entry.
- The block should only return values which are otherwise strongly referenced to ensure that the value is not immediately garbage-collected.
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.
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 |
# File 'lib/weak/map.rb', line 741 def update(*other_maps) if block_given? missing = Object.new other_maps.each do |map| _implicit(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| _implicit(map).each_pair do |key, value| self[key] = value end end end self end |
#values ⇒ Array
In contrast to a Hash, Weak::Maps 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 265
|
#values_at(*keys) ⇒ Array
818 819 820 |
# File 'lib/weak/map.rb', line 818 def values_at(*keys) keys.map { |key| self[key] } end |