slate

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.sizehow 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.sizehow 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]

Search

Esc
to navigate to open Esc to close