Pf::Map(K, V)
Inherits Enumerable / Pf::Kit / Struct / Value / Object
A thread-safe, persistent, unordered hash map.
Value equality
Being a persistent map, Pf::Map requires path copying. However, path
copying is unnecessary for equal values. If your values can be compared
cheaply, you can make them include Pf::Eq. Then an additional comparison
using == will take place to figure out whether path copying should occur.
Methods that are marked with supports value equality guarantee to return
exactly self if what they've done resulted in no change in self.
Note that out of the box, == is called only when your value is of the types
nil, Bool, Char, String, Symbol, or of a primitive number type. And
same? is called on all reference (Reference) types.
map = Pf::Map[foo: 100, bar: 200]
map.assoc("foo", 100).same?(map) # => true, no change
map.update("foo", 0, &.succ.pred).same?(map) # => true, no change
map2 = Pf::Map[foo: 100, bar: 200]
map.merge(map2).same?(map) # => true, no change
map2.merge(map).same?(map2) # => true, no change
If you want to enable == for your value, you should make them include
Pf::Eq.
record Info, first_name : String, last_name : String
people = Pf::Map
.assoc(0, Info.new("John", "Doe"))
.assoc(1, Info.new("Barbara", "Doe"))
people.assoc(0, Info.new("John", "Doe")).same?(people) # => false
# ^ Even though the value is the same the map is path-copied.
# v But if we include `Pf::Eq`...
record InfoEq, first_name : String, last_name : String do
include Pf::Eq
end
people = Pf::Map
.assoc(0, InfoEq.new("John", "Doe"))
.assoc(1, InfoEq.new("Barbara", "Doe"))
# ... no path copying is going to be done at the expense of comparing
# the values too.
people.assoc(0, InfoEq.new("John", "Doe")).same?(people) # => true
Since BidiMap is backed by Map, the same applies to it. On the
other hand, elements of a Set are keys so they are always compared
using == eventually.
Constructors
Constructs a Map from an enumerable of key-value pairs.
Class methods
A shorthand syntax for creating a Map with string keys. The type
of the map's values is the union of the types of values in entries.
map = Pf::Map[name: "John Doe", age: 25]
map["name"] # => "John Doe"
map["age"] # => 25
typeof(map) # => Pf::Map(String, String | Int32)
Returns true if two mapping values v1 and v2 are equal, taking
Pf::Eq into account.
Instance methods
Compares self with other. Returns true if all associations are
the same (values are compared using ==).
Returns the value associated with key. Raises KeyError if key
is absent.
map = Pf::Map[foo: 10]
map["foo"] # => 10
map["bar"] # raises KeyError
Traverses nested maps/Hashes and returns the value. Raises
KeyError if there is no value.
map = Pf::Map[foo: Pf::Map[bar: {100 => Pf::Map[baz: "Yay!"]}]]
map["foo", "bar", 100, "baz"] # => "Yay!"
map["foo", "bar", 200] # raises KeyError
Returns the value associated with key, or nil if key is absent.
map = Pf::Map[foo: 10, bar: 20]
map["foo"]? # => 10
map["bar"]? # => 20
map["baz"]? # => nil
Traverses nested maps/Hashes and returns the value, or nil if
the value is absent.
map = Pf::Map[foo: Pf::Map[bar: {100 => Pf::Map[baz: "Yay!"]}]]
map["foo", "bar", 100, "baz"]? # => "Yay!"
map["foo", "bar", 200]? # => nil
Returns a copy of self that contains the association between key and value.
Supports value equality.
map = Pf::Map(String, Int32).new
branch1 = map.assoc("foo", 100)
branch2 = map.assoc("foo", 200)
map = map.assoc("bar", 300)
map["foo"]? # => nil
map["bar"]? # => 300
branch1["foo"]? # => 100
branch1["bar"]? # => nil
branch2["foo"]? # => 200
branch2["bar"]? # => nil
Returns a new Map whose values are deeply cloned versions of
those from self. That is, returns a deep copy of self.
Keys are not cloned (if you need to clone keys then the last thing to help you is a persistent immutable map!).
map = Pf::Map[foo: [1, 2, 3], bar: [4, 5, 6]]
map2 = map.clone
map["foo"][0] = 100
map # => Pf::Map{"foo" => [100, 2, 3], "bar" => [4, 5, 6]}
map2 # => Pf::Map{"foo" => [1, 2, 3], "bar" => [4, 5, 6]}
Returns a copy of self without nil values.
map = Pf::Map[foo: nil, bar: 123]
map.compact # => Pf::Map{"bar" => 123}
typeof(map) # => Pf::Map(String, Int32?)
typeof(map.compact) # => Pf::Map(String, Int32)
Returns a new map with associations from self and other combined, where
other is an enumerable of key-value pairs.
Supports value equality.
map = Pf::Map[foo: 100, bar: 200, baz: 300]
map.concat([{"x", 123}, {"y", 456}]) # => Pf::Map[foo: 100, bar: 200, baz: 300, x: 123, y: 456]
Returns a copy of self that is guaranteed not to contain an association
with the given key.
map = Pf::Map[foo: 100, bar: 200]
branch1 = map.dissoc("foo")
branch2 = map.dissoc("bar")
map["foo"]? # => 100
map["bar"]? # => 200
branch1["foo"]? # => nil
branch1["bar"]? # => 200
branch2["foo"]? # => 100
branch2["bar"]? # => nil
Returns the value mapped to key, or yields if key is absent. This method mainly exists to circumvent nil as in value vs. nil as in absence issue.
Returns the value associated with key, or nil if the value is absent.
The value is wrapped in a tuple to differentiate between nil as value
and nil as absence.
map = Pf::Map[name: "John Doe", job: nil]
map.fetch?("job") # => {nil}
map.fetch?("name") # => {"John Doe"}
map.fetch?("age") # => nil
if name_t = map.fetch?("name")
name, *_ = name_t
name # => "John Doe"
end
Same as map, but returns Map instead of an array.
If the block returns more than one value for the same key, the last yielded value is preferred.
Supports value equality if K == K2 and V == V2.
map = Pf::Map[foo: "John Doe", bar: "Samantha Doe"]
map.fmap { |k, v| {k.upcase, v.upcase} } # => Pf::Map{"FOO" => "JOHN DOE", "BAR" => "SAMANTHA DOE"}
Returns true if key is present in this map.
map = Pf::Map[foo: 100, bar: 200]
"foo".in?(map) # => true
"bar".in?(map) # => true
"baz".in?(map) # => false
Returns an array with all keys from this map. There is no guaranteed order of keys.
map = Pf::Map[foo: 10, bar: 20]
map.keys # => ["foo", "bar"]
Transforms keys: same as fmap, but only yields keys from this map.
Supports value equality if K == K2.
map = Pf::Map[foo: "John Doe", bar: "Samantha Doe"]
map.map_key(&.upcase) # => Pf::Map{"FOO" => "John Doe", "BAR" => "Samantha Doe"}
Transforms values: same as fmap, but only yields values from
this map.
Supports value equality if V == V2.
map = Pf::Map[foo: "John Doe", bar: "Samantha Doe"]
map.map_value(&.upcase) # => Pf::Map{"foo" => "JOHN DOE", "bar" => "SAMANTHA DOE"}
Returns a new map with associations from self and other combined.
If some key is common both to self and other, other's value
is preferred.
Supports value equality if K == K2 and V == V2.
a = Pf::Map[foo: 100, bar: 200]
b = Pf::Map[foo: "hello", baz: true, boo: 500]
map = a.merge(b)
map # => Pf::Map{"foo" => "hello", "bar" => 200, "baz" => true, "boo" => 500}
typeof(map) # => Pf::Map(String, String | Int32 | Bool)
Returns a new map with assocations from self and other combined.
If some key is common both to self and other, that key is
yielded to the block together with the two values. The return
value of the block is used as the final value.
a = Pf::Map[foo: 100, bar: 200, baz: 300]
b = Pf::Map[foo: 200, bar: 300.8, boo: 1000.5]
map = a.merge(b) { |k, v1, v2| v1 + v2 }
map # => Pf::Map{"foo" => 300, "bar" => 500.8, "baz" => 300, "boo" => 1000.5}
typeof(map) # => Pf::Map(String, Int32 | Float64)
Returns the n-th association in this map. Their order is the same as
in each and is determined by the hash function. n can range from
0 to size - 1 (otherwise returns nil).
Returns a copy of self which includes only associations for which
the block is falsey.
Supports value equality.
map = Pf::Map[foo: 2, bar: 3, baz: 4, boo: 5]
map.reject { |_, v| v.even? } # => Pf::Map{"bar" => 3, "boo" => 5}
Returns a new map which is guaranteed not to include the given keys.
map = Pf::Map[foo: 2, bar: 3, baz: 4, boo: 5]
map.reject({"foo", "boo"}) # => Pf::Map{"bar" => 3, "baz" => 4}
map.reject("foo", "boo") # => Pf::Map{"bar" => 3, "baz" => 4}
Returns a new map which is guaranteed not to include the given keys.
map = Pf::Map[foo: 2, bar: 3, baz: 4, boo: 5]
map.reject({"foo", "boo"}) # => Pf::Map{"bar" => 3, "baz" => 4}
map.reject("foo", "boo") # => Pf::Map{"bar" => 3, "baz" => 4}
Returns true if self and other refer to the same map in memory.
Due to the way Map is implemented, this method can be used as
a cheap way to detect changes.
map1 = Pf::Map[foo: 123, bar: 456]
map2 = map1.assoc("foo", 123)
map1.same?(map2) # => true
Returns a copy of self which includes only associations for which
the block is truthy.
Supports value equality.
map = Pf::Map[foo: 2, bar: 3, baz: 4, boo: 5]
map.select { |_, v| v.even? } # => Pf::Map{"foo" => 2, "baz" => 4}
Returns a new map which includes only associations with the given keys.
map = Pf::Map[foo: 2, bar: 3, baz: 4, boo: 5]
map.select({"foo", "boo"}) # => Pf::Map{"foo" => 2, "boo" => 5}
map.select("foo", "boo") # => Pf::Map{"foo" => 2, "boo" => 5}
Returns a new map which includes only associations with the given keys.
map = Pf::Map[foo: 2, bar: 3, baz: 4, boo: 5]
map.select({"foo", "boo"}) # => Pf::Map{"foo" => 2, "boo" => 5}
map.select("foo", "boo") # => Pf::Map{"foo" => 2, "boo" => 5}
Yields a Commit object which allows you to mutate a copy of self.
-
The commit object is marked as resolved after the block. You should not retain it. If you do, all operations on the object (including readonly ones) will raise
ResolvedError. -
If you pass the commit object to another fiber in the block, e.g. via a channel, and fiber yield immediately after that, the commit obviously would not be marked as resolved as the resolution code would not have been reached yet. However, if you then attempt to call mutation methods on the commit, another error,
ReadonlyError, will be raised. In other words, the yielded commit object is readonly for any other fiber except for the fiber that it was originally yielded to.
Returns self if the transaction did not touch the map. If the map was
changed but then the changes were reverted this method will return a new map.
map1 = Pf::Map(String, Int32).new
map2 = map1.transaction do |commit|
commit.assoc("John Doe", 12)
commit.assoc("Susan Doe", 34)
commit.dissoc("John Doe")
if "John Doe".in?(commit)
commit.assoc("Mark Doe", 21)
else
commit.assoc("John Doe", 456)
commit.assoc("Susan Doe", commit["Susan Doe"] + 1)
end
end
map1 # => Pf::Map[]
map2 # => Pf::Map["John Doe" => 456, "Susan Doe" => 35]
Returns an updated copy of self.
-
If there is no association for key, the copy contains an association between key and default.
-
If there is an association for key, its value is yielded to the block and the return value of the block is used as the next value of key.
Supports value equality.
map = Pf::Map[foo: 100, bar: 200]
map.update("foo", 0, &.succ) # => Pf::Map{"foo" => 101, "bar" => 200}
map.update("baz", 0, &.succ) # => Pf::Map{"foo" => 100, "bar" => 200, "baz" => 0}
Returns an updated copy of self.
- If there is no association for key, returns
self. - If there is an association for key, its value is yielded to the block and the return value of the block is used as the next value of key.
Supports value equality.
map = Pf::Map[foo: 100, bar: 200]
map.update("foo", &.succ) # => Pf::Map{"foo" => 101, "bar" => 200}
map.update("baz", &.succ) # => Pf::Map{"foo" => 100, "bar" => 200}
Returns an array with all values from this map. There is no guaranteed order of values.
map = Pf::Map[foo: 10, bar: 20]
map.values # => [10, 20]