Basic expressions

Block expressions

An indented block is a kind of expression. Every function body is a block expression.
Block expressions can nest inside of other expressions.

main void() log "hello"

The beginning and ending of an indented block act like like parentheses.

main void() # Same as log 2 * (3 + 4) log 2 * 3 + 4

Like any expression, you need a comma before an indented block to separate it from other arguments.

main void() # Same as log abs -1 log abs -1 # Same as log 2, 3 log 2, 3

Sequence expressions

A sequence expression evaluates each subexpression in sequence.
Subexpressions are separated by newlines (at the same indent level).

main void() log 1 log 2

The value of a sequence expression is the value of the last line.
All other lines must be void.

main void() log foo foo string() log "side effect" "foo"

A sequence expression can go inside a block expression.

main void() x = log "in a local" 1 log log "in an argument" x

Locals

Creating a new local variable is simple: name = ...
A type after the name is optional.

main void() x = "hello" y string = "world" log x, y

mut makes the local mutable. := mutates the local.

main void() x mut = 1 x := 2 log x

If present, the type goes after mut.

main void() x mut nat = 1 x := 2 log x

Ignoring things

A sequence expression requires void subexpressions (except the last), but sometimes you want to ignore a non-void value.
To do so, assign it to _. You can even give it a type annotation if necessary.

main void() _ = 1 _ string = "foo" log "done"

_ works in parameters too.

main void() log foo 1 foo string(_ nat) "hello"

Scope

Locals can be nested inside other expressions.
The local is only in scope within its own expression.

main void() log x = 1 x log # This is a different 'x' x = "hello" x # log x # Won't work, 'x' is not in scope here