Use a type as a map key
An ordinary type is already a legal key once every field is Hash, which the compiler generates for free; a field that cannot be Hash is the one thing that rules a type out.
Map<Key, Value>'s implementations ask for Key: Hash, and Hash is generated for a type together with Equals
from the same fields, in the same order, whenever every field supports it. Most types qualify without a line written
for it; the ones that do not are ruled out by exactly one kind of field.
Steps#
Use the type as a key directly, if every field is
Hash.Int,String,Bool, anothertypewhose fields are allHash, and acasewhose fields are allHashall qualify already.type Point { x: Int y: Int }Check the one field that rules a type out: a function value. A field of a function type has no
EqualsorHashof its own, so a type that has one gets neither generated - move it out of the type that is used as a key, or key the map on the rest of the data instead.Reach for a tuple key when the key is several values that do not already live together in one type. A tuple's
EqualsandHashare generated the same way atype's are, from its elements in order.const cell: (Int, Int) = (3, 4) var seen: Map<(Int, Int), Bool> = [:] seen[cell] = trueWrite the map literal or call
Map.ofonce the key type is settled. Both inferKey: Hashfrom the values given, so nothing about the bound has to be spelled out at the call site.const distances: Map<Point, Int> = [Point(0, 0): 0, Point(3, 4): 5]
Pitfalls#
- A
shared typeis never a map key. It has no generatedEqualsorHashat all - it has an identity instead, andisSamecompares that, not content. Key on a field of it, or on an identifier the shared type carries. - A field that holds a
List,MaporSetstill works, as long as its own item and key types areHash. A collection has a generatedHashexactly like atypedoes, so nesting is not by itself what rules out a key. - Two keys that are
==must hash equally, and the compiler guarantees it by construction.Hashis generated from the same fieldsEqualscompares, in the same order, so there is nothing to keep in sync by hand. - Today's checker does not yet reject
Map<Key, Value>whenKeyhas a field that breaksHash- only a direct call of.hash()on such a value is rejected, ashash()was never generated. Writing a map keyed on a type with a function field is unspecified rather than caught early; the fields listed in step 1 are what to check by hand until it is.
Full example#
type Point {
x: Int
y: Int
}
const distances: Map<Point, Int> = [Point(0, 0): 0, Point(3, 4): 5]
print distances[Point(3, 4)]
const cell: (Int, Int) = (1, 1)
var visited: Map<(Int, Int), Bool> = [:]
visited[cell] = true
print visited.containsKey((1, 1))Related#
- Copy and equality - exactly when
EqualsandHashare generated. - Maps and sets - the literal, insertion order, and the
implementations
Key: Hashchooses between. - Tuples - the generated members a tuple gets the same way a
typedoes. - Shared types - why one has no generated
Hash, andisSamein its place.