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?` 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::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 single ‘ObjectSpace::WeakMap`. Instead, we use a sepate `WeakMap` for keys and values which in turn use the key’s ‘object_id` as a key. As these `ObjectSpace::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 an `ObjectSpace::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 given `key`; returns `value`.
-
#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; returns `default_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 to `key`.
-
#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` if `self` 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` if `value` is a value in `self`, `false` otherwise.
-
#include?(key) ⇒ Bool
(also: #has_key?, #key?, #member?)
‘true` if the given key is included in `self` 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 => value1, key2 => value2, …>”`.
-
#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 of `self`.
-
#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 ‘Array` of 2-element `Array` objects; each nested `Array` 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 in `self`.
-
#update(*other_maps) {|key, old_value, new_value| ... } ⇒ self
(also: #merge!)
Merges each of ‘other_maps` into `self`; returns `self`.
-
#values ⇒ Array
An ‘Array` containing all values of the map for which we have a valid key.
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`
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
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.
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 |
permalink #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 |
permalink #compare_by_identity? ⇒ true
Returns always ‘true` since we always compare elements by their object identity.
362 363 364 |
# File 'lib/weak/map.rb', line 362 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.
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 |
permalink #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 |
permalink #default_proc ⇒ Proc?
Returns the default proc for ‘self`.
395 396 397 |
# File 'lib/weak/map.rb', line 395 def default_proc @default_proc end |
permalink #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 |
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 #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.
436 437 438 |
# File 'lib/weak/map.rb', line 436 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.
446 447 448 449 |
# File 'lib/weak/map.rb', line 446 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.
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 |
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 => value1, key2 => value2, …>”`.
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 |
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.
542 543 544 |
# File 'lib/weak/map.rb', line 542 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 #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.
641 642 643 |
# File 'lib/weak/map.rb', line 641 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`.
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 |
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.
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 |
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
|