Collections
An array holds things in order and an object holds things under names. A set holds each value once, and a map holds a value under any other value at all — not only under a string.
Both are built by a global of the same name, and both keep the order things arrived in.
val seen = Set(["b", "a", "b"])
val ages = Map([["ada", 36], ["alan", 41]])
print(seen, ages)
["b", "a"] [["ada", 36], ["alan", 41]]
Any value is a key
This is the whole reason the two exist. An object’s field names are strings, so a program that wants to count occurrences of an array, or to hold a record under a point, has to invent a spelling for the key and hope nothing else spells the same thing. A set and a map take the value itself.
val corners = Set()
corners.add([1, 2])
corners.add([1, 2])
corners.add([1, 3])
print(corners.size, corners.has([1, 2]))
2 true
A key is compared exactly the way == compares it, which is by value all the way down — so two
arrays written alike are one key. null is a key like any other.
val m = Map()
m.set(null, "the value null")
m.set("null", "the word")
print(m.size, m.get(null), m.get("null"))
2 the value null the word
A class that decides its own equality decides its own keys
A set finds a member by its hash and compares only the members that hashed alike. So a class that
writes == must write hash beside it to be usable as a key: two equal values that hashed apart
would never be compared, and the set would hold both.
class Point
var x
var y
==(self, o) = o is Point && o.x == self.x && o.y == self.y
hash(self) = self.x * 31 + self.y
val visited = Set()
visited.add(Point(1, 2))
visited.add(Point(1, 2))
print(visited.size, visited.has(Point(1, 2)), visited.has(Point(9, 9)))
1 true false
Writing == and leaving hash out is not refused — it is the rule
Objects states, and a set is where breaking it shows.
What a set can do
s.size | how many members, a property and not a call |
s.add(v) | adds v, answers the set |
s.has(v) | whether v is in it |
s.delete(v) | removes v, answers whether there was one |
s.clear() | removes everything |
s.values() | the members, as an array |
s.forEach(f) | calls f on each member |
add answers the set so a chain reads as building one, and delete answers whether there was
something to delete so a program need not ask has first.
val s = Set().add(1).add(2).add(3)
print(s.delete(2), s.delete(2), s)
true false [1, 3]
What a map can do
m.size | how many pairs, a property and not a call |
m.set(k, v) | writes v under k, answers the map |
m.get(k) | the value, or null where there is none |
m.has(k) | whether the key is there |
m.delete(k) | removes the pair, answers whether there was one |
m.clear() | removes everything |
m.keys(), m.values(), m.entries() | arrays |
m.forEach(f) | calls f on each [key, value] pair |
get answers null for a key that is not there, and never undefined — slate
stores no absence. has is what tells a stored null from a missing key.
val m = Map([["a", null]])
print(m.get("a"), m.get("b"), m.has("a"), m.has("b"))
null null true false
Writing a key that is already there keeps its position and replaces the value, which is what makes the order a map walks in the order its keys were first written.
val m = Map()
m.set("first", 1)
m.set("second", 2)
m.set("first", 10)
print(m.keys(), m.values())
["first", "second"] [10, 2]
size is a property
s.size and m.size are read with no brackets after them, as
.length is on an array or a string. Calling one is a mistake the compiler names.
print(Set([1, 2]).size())
`size` is a property, not a method
.length is not a set’s or a map’s. Neither is a sequence, nothing in one is at a position, and
a program reaching for .length has confused it with an array.
print(Set([1, 2]).length)
`length` is not something a set can do
Building one from anything walkable
Set(x) takes an array, a range, a generator, another set, or a map; Map(x) reads a list of
pairs, which is the shape entries() answers with — so Map(m.entries()) copies a map, and so does
Map(m).
print(Set([3, 1, 2, 3]))
print(Set(0..<4))
print(Set(Set([5, 6])))
print(Map(Map([["a", 1]])))
print(Set(), Map())
[3, 1, 2]
[0, 1, 2, 3]
[5, 6]
[["a", 1]]
[] []
A pair is exactly two values, and anything else is named where it was found.
print(Map([["a", 1, 2]]))
one of these has 3 values rather than a key and a value
Walking one
A set yields its members and a map yields its pairs — one answer to “what is in this”, which
for, ... and forEach all give.
val s = Set([1, 2])
val m = Map([["a", 1], ["b", 2]])
for v in s
print("member", v)
for [k, v] in m
print("pair", k, v)
print([...s], [...m])
member 1
member 2
pair a 1
pair b 2
[1, 2] [["a", 1], ["b", 2]]
Removing an entry does not move the ones after it, so a walk gives back what is left in the order it was written.
val s = Set([1, 2, 3, 4, 5])
s.delete(1)
s.delete(3)
print([...s])
[2, 4, 5]
Printing, JSON, and equality
A set prints as the array of its members and a map as the array of its pairs, and toJSON
writes the same shape — one rendering rather than two, so a value written out and read back is the
same thing in both directions. (JavaScript prints Set(2) {1, 2}, which nothing parses, and encodes
a Map as {}, which is empty.)
print(Set([1, 2]), Map([["a", 1]]))
print(toJSON(Set([1, 2])), toJSON(Map([["a", 1], ["b", 2]])))
[1, 2] [["a", 1]]
[1,2] [["a",1],["b",2]]
A set and a map compare by IDENTITY, as a promise and a function do. A container whose contents can change cannot compare by them, or an answer would stop being true without either value being touched by the code that asked.
val one = Set([1])
val other = Set([1])
print(one == one, one == other)
true false
In the type language
Set and Map are words a pattern is written with, and each takes a member type in
brackets: Set[T], and Map[K, V].
print(Set([1, 2]) is Set, Map([["a", 1]]) is Map, [1] is Set)
print(Set([1, 2]) is Set[integer], Set([1, "x"]) is Set[integer])
print(Map([["a", 1]]) is Map[string, integer], Map([["a", 1]]) is Map[string, string])
true true false
true false
true false
A member type is tested member by member, at run time, which is what makes it a claim the machine
checks rather than a note to the checker — array of T is the same arrangement. An empty set fits
every member type, as an empty array does.
type Names = Set[string]
val bad = Set(["a", 2])
for problem in Names.mismatch(bad)
print(problem.path, problem.wanted, problem.got)
2 string integer
The checker reports a member type only against an annotation the program declared, since the machine takes any key at all and a sharper inference would refuse programs that run.
val s: Set[string] = Set([1, 2, 3])
declared Set[string], and this is Set[integer]