slate

Values

slate is dynamically typed. Every value is one of a fixed set of kinds, and a program can ask which.

The kinds

kindwrittennotes
nullnullthe only absence there is
booleantrue, false
integer42, 0xff64 bits, signed, wraps
real3.14, 2e10a double
string"text"a sequence of characters, never of bytes
array[1, 2]reference type, compares by contents
object{ a: 1 }reference type, compares by contents
functionx -> x, a definition
promiseanswered by an async callsee Asynchrony

Eight more scalar kinds come with slate:timeinstant, duration, date, time, dateTime, zone, zoned, period — and a compiled pattern comes with slate:regex. Each has a type word that tests for it in pattern position.

number is not a kind. It is the union of integer and real, and exists because the two are separate values: a guard about arithmetic would otherwise have to be written twice.

Truth

Only false and null are false. Zero, the empty string and the empty array are all true.

print(if 0 then "true" else "false")
print(if "" then "true" else "false")
print(if [] then "true" else "false")
true
true
true

Equality

== compares by value, all the way down. Two arrays holding the same elements are equal; two objects holding the same fields are equal.

print([1, 2] == [1, 2], { a: 1 } == { a: 1 })
true true

An integer and a real compare across the kinds, so 1 == 1.0 is true — but 1 is integer and 1.0 is integer still differ, the two being distinct values that happen to compare equal.

A function is equal to itself. f == f is true, and two separately written lambdas with the same body are not equal.

A class may take == over for its own instances by writing equals, and must write hash beside it if those instances are to be used as table keys. <, <=, > and >= are one hook, compare — see Objects.

Numbers

An integer is 64 bits and wraps; it does not promote to a real and does not become a big integer.

print(9223372036854775807 + 1)
print(1 << 40)
-9223372036854775808
1099511627776

/ between two integers divides towards zero and answers an integer; % takes the sign of the left operand. Where either operand is a real the answer is a real.

print(7 / 2, 7.0 / 2, -7 / 2)
3 3.5 -3

A real that is whole prints as an integer does. string(1.0) is "1". Only %, indexing, or a kind test can tell the two apart, so a function that must answer an integer is worth annotating.

Strings

A slate string is a sequence of characters, so every position, length and slice is counted the way a person counts:

print(len("日本語"))
print("日本語"[0..<1])
print("héllo"[1])
print(indexOf("héllo", "llo"))      // by character; it is 3 by byte
3
日
é
2

There is no character type: a single character is a string of one. That is what lets indexing, chars and split all hand back the same kind of thing.

toBytes(s) answers an array of numbers and fromBytes(bs) answers a result; those two are the only place a slate program sees UTF-8, and len(toBytes(s)) is the byte count.

Case and whitespace are the whole database and not the ASCII range. upper and lower answer what any other language with a case table answers, which is not always one character out for one character in, and trim takes off Unicode’s White_Space — so a no-break space pasted out of a form comes off and a zero-width no-break space, which is not a space at all, stays.

print(upper("Straße"), len(lower("İ")))
print(lower("ΟΔΟΣ"), lower("ΟΔΟΣΑ"))
print("[" + trim(" \u{a0}x\u{a0} ") + "]")
STRASSE 2
οδος οδοσα
[x]

The second line is Unicode’s own rule that a sigma ending a word is written ς, which is context rather than a table; the first is ß uppercasing to two letters and İ lowercasing to two.

normalize(s, form) puts text into one of "NFC", "NFD", "NFKC" and "NFKD", which is what two strings have to go through before == between them means what a reader thinks it means — the same word typed on two machines is routinely two different sequences of characters. casefold(s) is what two strings differing only in case both come to, and is not lower: ß folds to ss, so casefold("STRASSE") == casefold("Straße") where lowering leaves them different.

Conversion

The type words are the conversions. string, number, integer, real and boolean each test in pattern position and convert in expression position; the two never overlap.

print(string(123) + "!")
print(number("42"), number("nonsense"))
print(integer(2.9), real(3), real(3) is real)
123!
42 null
2 3 true

number answers null where the text is not a number, which is how a program checks input without raising. integer and real move between the two numeric kinds; the four roundings — floor, ceil, round, trunc — leave an integer alone.

min and max take as many arguments as they are given and answer an integer when every one of them was.

Reference and copy

Arrays and objects are reference types: two names may hold the same array, and a write through one is visible through the other. Everything else is a scalar.

a with { f: v } answers a copy of a with f changed, which is how a record is updated without mutating it. concat(xs, ys) is the array counterpart.

Absence

There is no undefined. null is the only absence, it is an ordinary value, and slate refuses to store anything else in its place. That single rule explains a run of behaviour that otherwise looks unrelated:

  • pop, shift and at fault where there is nothing there, rather than answering nothing.
  • find and indexOf answer null — a search that found nothing is an answer, where reaching past the end is a mistake.
  • A parameter nobody gave is not bound at all, so there is no sentinel to test for and f(1, null) is not the same as f(1).
  • An object field a value need not have is a question about its shape (pinned?), not a value that might be absent.

Search

Esc
to navigate to open Esc to close