Statements
Declarations
val name = "slate"
var n = 0
val cannot be assigned to again; var can. A val bound to an array or an object still allows
that container to be changed — the binding is what is fixed, not the value.
Either may say what it holds, and an annotated var is TypeScript’s let: the declared type is what
the name holds for its whole life, so every assignment is checked against it.
val name: string = "slate"
var n: integer = 0
n += 1
print(name, n)
slate 1
A definition is a statement too, and so are type, class and data, each of which
belongs to the top level of a file.
What is in scope where
A definition is HOISTED to the top of the block it is written in, which is JavaScript’s rule, so a name may be used above the line that defines it:
val sink = written
written(r) = "wrote " + string(r)
print(sink(1))
wrote 1
A val or var initialiser stays sequential — it may read what is above it and not what is
below — and that is the half that matters: nothing about hoisting makes a program’s data flow depend
on where a function happens to sit.
val a = b
val b = 1
print(a)
`b` is not defined
The definitions run where they are hoisted, so a statement standing between two of them is not reordered around anything: only the binding moves, and what it binds is a function that has not been called yet.
A definition may not take a name its own block has already declared, whether by another
definition or by a val or a var. The hoisting is the reason: the two would not run in the order
they are written, so what the second one means depends on a rule the reader cannot see.
f() = 1
print(f())
f() = 2
`f` is declared twice in this block
A nested block is its own, so shadowing an outer name is untouched — and a val written below
a definition is not a collision but the ordinary way to name a generator and then run it:
counting()
yield 1
yield 2
val counting = counting()
print(counting.next().value, counting.next().value)
1 2
Assignment
Assignment is a statement, never an expression, so = cannot appear inside an expression and
there is nothing for it to be confused with.
var n = 0
var xs = [1, 2]
var o = { f: 0 }
var a = 1
var b = 2
n = 3
xs[0] = 9
o.f = 9
a, b = b, a // several places at once
print(n, xs, o, a, b)
3 [9, 2] {f: 9} 2 1
Assignment binds nothing. g = 1 where g was never declared is refused before the program runs,
naming the nearest name it does know; val and var are the only things that introduce a name.
The compound forms are += -= *= /= %= and the bitwise &= |= ^= <<= >>=. A compound form
evaluates its place once, so xs[next()] += 1 calls next a single time.
++ and -- step a name, a field or an element, prefix or postfix.
if
grade(mark)
if mark >= 90
"A"
elif mark >= 80
"B"
else
"C"
print(grade(95), grade(85), grade(20))
A B C
The inline form takes then, and its body is a statement, not an expression — which is the rule
that makes the short forms worth having:
first_big(xs)
for x in xs
if x <= 2 then continue
if x > 2 then return x
null
print(first_big([1, 2, 7, 9]), first_big([1, 2]))
7 null
An if is an expression when every branch answers one: val g = if c then 1 else 2.
Loops
Three of them, and every one is an expression:
var c = 3
while c > 0
c -= 1
for x in [1, 2]
print(x)
var n = 0
loop
n += 1
if n == 2 then break
print(c, n)
1
2
0 2
do introduces a one-line body — while c do …, for x in xs do …, loop do ….
for await x in source is the fourth, and it walks something that answers next() a value at a
time — see Asynchrony.
A for head may take its element apart with a pattern:
for [k, v] in entries({ a: 1, b: 2 })
print(k, v)
a 1
b 2
What a loop answers
break is what gives a loop a value. A loop that finishes on its own answers null, or whatever
its else clause left:
find_first(xs, wanted)
for i in 0..<len(xs) do
if xs[i] == wanted then break i
else
-1
end find_first
print(find_first([4, 5, 6], 5), find_first([4, 5, 6], 9))
1 -1
The else clause runs when the loop ended without a break, and its value is the loop’s.
Labels
A label says which loop a break leaves, which is the only way out of a nested one:
val found = 'search for a in [1, 2, 3]
for b in [4, 5]
if a * b == 8 then break 'search [a, b]
print(found)
[2, 4]
continue starts the next turn, and takes a label the same way.
Blocks
A block’s value is its trailing expression. return is for leaving a function early and nothing else,
so a function whose last statement is its answer does not write one.
counter()
var count = 0
bump()
count = count + 1
count
bump
val c = counter()
print(c(), c(), c())
1 2 3
throw
throw v is a statement, like return and break — nothing after it runs, and the value is not
optional. See Faults.
Closing words
end if, end while, end for, end loop, and end <name> for a definition, a class or a data
type. All are optional and all are for a block long enough to want one.