Sets, Data and maps¶
Three kinds of value are written in braces, or hold keys and values, and they are easy to mix up. They are different types with different jobs:
| Type | What it is | Written |
|---|---|---|
Set<T> |
distinct values of one type, with no positions | {1, 2, 3} |
Data |
a tree of named fields of any types, the shape of JSON | {title: "Dune", pages: 412} |
Map<K, V> |
keys of one type, each to a value of one type | Map(["dune"], [412]) |
The colon tells braces apart¶
Braces holding values are a set; braces holding key: value entries are
Data. The colon is the only thing that tells them apart, and the empty forms
keep it: {} is the empty set and {:} the empty Data.
{3, 1, 3, 2}
{3, 1, 2}
{title: "Dune", pages: 412}
{pages: 412, title: "Dune"}
{}
{}
{:}
{:}
A set keeps each value once. A Data record prints its keys in order, so the
same tree always prints the same way.
{} has a definite meaning, the empty set, so it is not an empty record:
d : Data = {}
d
type error: `d` is declared Data but its value is Set<Never>
--> <expression>:1:12
|
1 | d : Data = {}
| ^^
{} is a Set<Never>: a set with no elements, and so no element type yet. A
declared set type gives it one:
seen : Set<Int> = {}
seen | size
0
Data is not a map¶
Data is the type of values whose shape is not known to the program: what a
JSON or YAML document decodes to. A record's fields may hold different types,
a number beside a text beside a list, and nesting goes as deep as the
document does. It is not a hash map, and not a Map<String, Any>.
A Map<K, V> is a typed mapping: every key is a K and every value a V.
The constructor pairs a list of keys with a list of values:
Map(["dune", "emma"], [412, 474])
Map {"dune": 412, "emma": 474}
Map(["dune", "emma"], [412, 474]).get("dune")
412
So the three answer different needs: a set, which values; a map, which
value for each key, all of one type; Data, whatever structure arrived.
{:} is Data, or a declared map¶
{:} has no type of its own. Where nothing says otherwise it is the empty
Data; where a map type is declared for it, it is that empty map:
| Written | Type | Value |
|---|---|---|
{:} with nothing around it |
Data |
the empty Data record, printed {:} |
d : Data = {:} |
Data |
the same |
m : Map<String, Int> = {:} |
Map<String, Int> |
an empty map, equal to Map<String, Int>([], []) |
m : SortedMap<String, Int> = {:}, or OrderedMap |
that map type | an empty map of that kind |
f({:}) where f takes a Map<String, Int> |
Map<String, Int> |
an empty map |
counts : Map<String, Int> = {:}
counts == Map<String, Int>([], [])
true
Data is the default, and a declared Map, OrderedMap or SortedMap turns
it into that empty map. An empty literal carries no information of its own,
so the type around it decides: HQL infers a type while the literal says
enough, and asks for an explicit one where it does not.
This holds for the empty literal only. A non-empty keyed literal is always
Data, and a declared map type refuses it:
m : Map<String, Int> = {a: 1}
m
type error: `m` is declared Map<String, Int> but its value is Data
--> <expression>:1:24
|
1 | m : Map<String, Int> = {a: 1}
| ^^^^^^
A non-empty map is built with the constructor, Map(["a"], [1]).
What is next¶
Chapter 3 uses lists, sets and maps as collections, and the steps they share.