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 more...
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#hash
as theHash
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::WeakMap
with 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::WeakMap
with 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::WeakMap
with 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 sepateWeakMap
for keys and values which in turn use the key'sobject_id
as a key. As theseObjectSpace::WeakMap
objects 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::WeakMap
as 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
STRATEGY
is 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
value
with 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::WeakMap
implementation. -
#compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
-
#compare_by_identity? ⇒ true
Always
true
since 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_value
and 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
proc
and clears the #default value. -
#delete(key = UNDEFINED) {|key| ... } ⇒ Object?
Deletes the key-value pair and returns the value from
self
whose key is equal tokey
. -
#delete_if {|key, value| ... } ⇒ Enumerator, self
Deletes every key-value pair from
self
for 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
true
ifself
contains 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::WeakMap
implementation. -
#has_value?(value) ⇒ Bool
(also: #value?)
true
ifvalue
is a value inself
,false
otherwise. -
#include?(key) ⇒ Bool
(also: #has_key?, #key?, #member?)
true
if the given key is included inself
and has an associated live value,false
otherwise. -
#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
self
for which the given block evaluates to a falsey value. -
#keys ⇒ Array
An
Array
containing 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_maps
into 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
self
for which the given block evaluates to a truethy value. -
#select! {|key, value| ... } ⇒ Enumerator, ...
(also: #filter!)
Deletes every key-value pair from
self
for 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
Array
of 2-elementArray
objects; each nestedArray
contains a key-value pair from self. -
#to_h {|key, value| ... } ⇒ Hash
A new
Hash
which 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_maps
intoself
; returnsself
. -
#values ⇒ Array
An
Array
containing all values of the map for which we have a valid key. -
#values_at(*keys) ⇒ Array
Returns a new
Array
containing values for the given keys:.
Constructor Details
permalink #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
315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/weak/map.rb', line 315 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
permalink .[](*maps) ⇒ Weak::Map
281 282 283 |
# File 'lib/weak/map.rb', line 281 def self.[](*maps) Weak::Map.new.merge!(*maps) end |
Instance Method Details
permalink #[](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
|
permalink #[]=(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
|
permalink #clear ⇒ self
Removes all elements and returns self
|
# File 'lib/weak/map.rb', line 233
|
permalink #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.
345 346 347 348 349 |
# File 'lib/weak/map.rb', line 345 def clone(freeze: false) warn("Can't freeze #{self.class}") if freeze super(freeze: false) end |
permalink #compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
355 356 357 |
# File 'lib/weak/map.rb', line 355 def compare_by_identity self end |
permalink #compare_by_identity? ⇒ true
Returns always true
since we always compare elements by their
object identity.
361 362 363 |
# File 'lib/weak/map.rb', line 361 def compare_by_identity? true end |
permalink #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.
374 375 376 377 378 379 380 |
# File 'lib/weak/map.rb', line 374 def default(key = UNDEFINED) if UNDEFINED.equal? key @default_value else _default(key) end end |
permalink #default=(default_value) ⇒ Object
Sets the default value to default_value
and clears the #default_proc;
returns default_value
.
388 389 390 391 |
# File 'lib/weak/map.rb', line 388 def default=(default_value) @default_proc = nil @default_value = default_value end |
permalink #default_proc ⇒ Proc?
Returns the default proc for self
.
394 395 396 |
# File 'lib/weak/map.rb', line 394 def default_proc @default_proc end |
permalink #default_proc=(proc) ⇒ Proc?
Sets the default proc for self to proc
and clears the #default value.
406 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 |
# File 'lib/weak/map.rb', line 406 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 |
permalink #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
|
permalink #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.
445 446 447 448 449 450 451 452 |
# File 'lib/weak/map.rb', line 445 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 |
permalink #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
|
permalink #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
|
permalink #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
|
permalink #empty? ⇒ Boolean
Returns true
if self
contains no elements.
455 456 457 |
# File 'lib/weak/map.rb', line 455 def empty? size == 0 end |
permalink #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
|
permalink #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.
465 466 467 468 |
# File 'lib/weak/map.rb', line 465 def freeze warn("Can't freeze #{self.class}") self end |
permalink #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.
474 475 476 477 |
# File 'lib/weak/map.rb', line 474 def has_value?(value) id = value.__id__ each_value.any? { |v| v.__id__ == id } end |
permalink #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
|
permalink #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, ...}>"
.
483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/weak/map.rb', line 483 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 |
permalink #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.
508 509 510 511 512 513 514 515 |
# File 'lib/weak/map.rb', line 508 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 |
permalink #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
|
permalink #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’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 hashes 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.
581 582 583 |
# File 'lib/weak/map.rb', line 581 def merge(*other_maps, &block) dup.merge!(*other_maps, &block) end |
permalink #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
|
permalink #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.
deleted, or an Enumerator
if no block was given.
see #delete_if
606 607 608 609 610 611 612 613 614 615 616 617 618 |
# File 'lib/weak/map.rb', line 606 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 |
permalink #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.
634 635 636 637 638 639 640 641 642 643 644 645 646 |
# File 'lib/weak/map.rb', line 634 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 |
permalink #size ⇒ Integer Also known as: length
Returns the number of live key-value pairs in self
.
|
# File 'lib/weak/map.rb', line 260
|
permalink #to_a ⇒ Array
Returns a new Array
of 2-element Array
objects; each nested
Array
contains a key-value pair from self.
736 737 738 |
# File 'lib/weak/map.rb', line 736 def to_a to_h.to_a end |
permalink #to_h {|key, value| ... } ⇒ Hash
Returns a new Hash
which considers object identity for keys which
contains the key-value pairs in self
.
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 |
# File 'lib/weak/map.rb', line 748 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 |
permalink #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’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 hashes 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.
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 |
# File 'lib/weak/map.rb', line 711 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 |
permalink #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
|
permalink #values_at(*keys) ⇒ Array
788 789 790 |
# File 'lib/weak/map.rb', line 788 def values_at(*keys) keys.map { |key| self[key] } end |