struct Pf::Map(K, V)
- Pf::Map(K, V)
- Struct
- Value
- Object
Overview
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.
Included Modules
Defined in:
permafrost/map.crConstructors
-
.new(enumerable : Enumerable(Tuple(K, V)))
Constructs a
Map
from an enumerable of key-value pairs. -
.new : Map(K, V)
Constructs an empty
Map
.
Class Method Summary
-
.[](**entries)
A shorthand syntax for creating a
Map
with string keys. -
.assoc(key : K, value : V) : Map(K, V)
A shorthand for
new.assoc
. -
.eqv?(v1 : V, v2 : V) : Bool forall V
Returns
true
if two mapping values v1 and v2 are equal, takingPf::Eq
into account. -
.transaction(& : Commit(K, V) -> ) : Map(K, V)
Shorthand for
new.transaction
.
Instance Method Summary
-
#==(other : Map) : Bool
Compares
self
with other. -
#[](key : K) : V
Returns the value associated with key.
-
#[](key : K, *subkeys)
Traverses nested maps/
Hash
es and returns the value. -
#[]?(key : K) : V | Nil
Returns the value associated with key, or nil if key is absent.
-
#[]?(key : K, *subkeys)
Traverses nested maps/
Hash
es and returns the value, ornil
if the value is absent. -
#assoc(key : K, value : V) : Map(K, V)
Returns a copy of
self
that contains the association between key and value. -
#clone : Map(K, V)
Returns a new
Map
whose values are deeply cloned versions of those fromself
. -
#compact
Returns a copy of
self
withoutnil
values. -
#concat(other : Enumerable(Tuple(K, V))) : Map(K, V)
Returns a new map with associations from
self
and other combined, where other is an enumerable of key-value pairs. -
#dissoc(key : K) : Map(K, V)
Returns a copy of
self
that is guaranteed not to contain an association with the given key. -
#each(& : Tuple(K, V) -> ) : Nil
Yields each association to the block.
-
#each_key(& : K -> ) : Nil
Yields each key to the block.
-
#each_value(& : V -> ) : Nil
Yields each value to the block.
-
#empty? : Bool
Returns
true
if this map contains no associations. -
#fetch(key : K, & : -> {K, V}) : V | {K, V} forall T
Returns the value mapped to key, or yields if key is absent.
-
#fetch?(key : K) : Tuple(V) | Nil
Returns the value associated with key, or
nil
if the value is absent. -
#fmap(& : Tuple(K, V) -> Tuple(K2, V2)) : Map(K2, V2) forall K2, V2
Same as
map
, but returnsMap
instead of an array. -
#has_key?(key) : Bool
Alias of
#includes?
. -
#hash(hasher)
See
Object#hash(hasher)
-
#includes?(key : K) : Bool
Returns
true
if key is present in this map. - #inspect(io)
-
#keys : Array(K)
Returns an array with all keys from this map.
-
#map_key(& : K -> K2) : Map(K2, V) forall K2
Transforms keys: same as
#fmap
, but only yields keys from this map. -
#map_value(& : V -> V2) : Map(K, V2) forall V2
Transforms values: same as
#fmap
, but only yields values from this map. -
#merge(other : Map(K2, V2)) : Map(K | K2, V | V2) forall K2, V2
Returns a new map with associations from
self
and other combined. -
#merge(other : Map(K2, V2), & : K, V, V2 -> V | V2) : Map(K | K2, V | V2) forall K2, V2
Returns a new map with assocations from
self
and other combined. - #pretty_print(pp) : Nil
-
#reject(& : Tuple(K, V) -> Bool) : Map(K, V)
Returns a copy of
self
which includes only associations for which the block is falsey. -
#reject(keys : Enumerable)
Returns a new map which is guaranteed not to include the given keys.
-
#reject(*keys)
Returns a new map which is guaranteed not to include the given keys.
-
#same?(other : Map(K, V)) : Bool
Returns
true
ifself
and other refer to the same map in memory. -
#select(& : Tuple(K, V) -> Bool) : Map(K, V)
Returns a copy of
self
which includes only associations for which the block is truthy. -
#select(keys : Enumerable)
Returns a new map which includes only associations with the given keys.
-
#select(*keys)
Returns a new map which includes only associations with the given keys.
-
#size : Int32
Returns the number of associations in this map.
- #to_s(io)
-
#transaction(& : Commit(K, V) -> ) : Map(K, V)
Yields a
Commit
object which allows you to mutate a copy ofself
. -
#update(key : K, default : V, & : V -> V)
Returns an updated copy of
self
. -
#update(key : K, & : V -> V) : Map(K, V)
Returns an updated copy of
self
. -
#values : Array(V)
Returns an array with all values from this map.
Instance methods inherited from module Enumerable({K, V})
to_pf_bidi
to_pf_bidi,
to_pf_map(& : {K, V} -> Tuple(K, V)) : Pf::Map(K, V) forall K, Vto_pf_map to_pf_map, to_pf_set : Pf::Set({K, V}) to_pf_set
Constructor Detail
Constructs a Map
from an enumerable of key-value pairs.
Class Method Detail
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.
Shorthand for new.transaction
.
Instance Method Detail
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/Hash
es 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/Hash
es 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 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]