slate

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.

Search

Esc
to navigate to open Esc to close