Hello, world
say writes a line of output. Throughout these pages, lines beginning
with >> show the program's expected output — they are not part of
the program.
say "Hello, world"
>> Hello, world
Strings interpolate anything between braces — not just variable names, but any expression:
my who ("world")
say "Hello, {who} — and 2 + 2 is {2 + 2}"
>> Hello, world — and 2 + 2 is 4
The two whitespace rules
Glue's identity rests on two rules about whitespace. Once you know them, most of the language follows.
1. Blocks are indentation — tabs only, and the head line ends in :
Like Python, a block is marked by indenting, not by braces or end. Unlike
Python, indentation must be tab characters: one tab per level, and a
space at the start of a line is a syntax error. There is exactly one way to indent.
if age < 18:
say "minor"
The line that introduces a block ends in a glued : — and a line ending
in : requires an indented block under it. Block structure is
therefore stated twice, by the colon and by the indentation, so an indentation slip is
an error message instead of a silently different program.
2. The adjacency rule
Horizontal spacing matters too. A symbol standing alone (space on both sides) is an operator; a symbol glued to its neighbour is part of the token. The same characters mean different things depending on spacing:
| You write | Glue reads |
|---|---|
total(x) | call the function total with argument x |
total (x) | assign the value of x to the variable total |
a-b | one identifier named a-b (hyphens are legal inside names) |
a - b | subtraction |
-a | negation of a |
Isn't that fragile? One space changing the meaning sounds dangerous, so the language compensates by being loud: when spacing doesn't produce a legal reading, that's a hard compile error, not a guess. Much of the design deliberately arranges for the mistyped version of a line to be illegal rather than quietly different.
Variables
Two things surprise most newcomers. First, a variable must be declared
the first time it is bound — my declares it in the current block. Second,
there is no = assignment. Assignment is the name followed by
a spaced parenthesis group: name (value).
# declare count, starting at 0
my count (0)
# reassign: name, space, (value)
count (count + 1)
say count
>> 1
The = sign is pure equality, as in mathematics:
if count = 1:
say "count is one"
>> count is one
Assigning to a name that was never declared is a compile error, which catches typos at the moment they happen:
my total (5)
# ERROR: 'totle' was never declared
totle (total + 1)
Besides my there are up (declare in the enclosing block) and
global (file scope). Declaring a name that is already visible is also an
error — no shadowing, ever.
Strings
Double quotes interpolate; single quotes are raw:
say 'no {interpolation} in raw strings'
>> no {interpolation} in raw strings
There are no backslash escape sequences. A backslash in a string is just a backslash. Special characters are ordinary named constants that you interpolate:
say "first line{NL}second line"
>> first line
>> second line
A list won't silently flatten into a string — you must say how to join it:
my parts (["alpha", "beta", "gamma"])
say "parts: {parts.join(', ')}"
>> parts: alpha, beta, gamma
(There is no string concatenation operator at all. Interpolation and
.join cover it.)
Making decisions
if you already know. or at the start of a line is
“else if”, and else is the fallback:
if age < 18:
say "minor"
or age < 21:
say "US minor"
else:
say "adult"
A short statement can carry its condition at the end instead:
say "quiet in here" if volume < 3
When a decision is really a table, write it as one. choose tries each row
top to bottom, and the first condition that holds supplies the value — here assigned
into advice:
my advice (choose:
age < 18 "Too young to drink"
age < 21 "Too young to drink in the US"
default "Legal drinking age"
)
say advice
Loops
for over a range (.. is inclusive, ..<
excludes the end):
for i (1 .. 5):
say "beat {i}"
>> beat 1
>> beat 2
>> beat 3
>> beat 4
>> beat 5
for over a list:
my scores ([70, 85, 90])
my total (0)
for s (scores):
total (total + s)
say "total {total}"
>> total 245
And while, for as long as a condition holds:
my n (1)
while n < 100:
n (n * 2)
say n
>> 128
Your first function
A function definition is the function's name with a glued leading dot —
no def or function keyword. Parameters follow bare, and the
head line ends in : like every line that introduces a block:
.greet "who", punct ('!'):
say "Hello, {who}{punct}"
greet old friend of mine
>> Hello, old friend of mine!
Three things to notice. The quoted parameter "who" is an
implicit-string parameter, so the call site may pass bare text — no quotes
needed. punct ('!') gives a default value. And the call is just the bare
name greet followed by its arguments.
Values come back with an explicit return — always. A final parameter
spelled rest[] collects any number of remaining arguments:
.sum first, rest[]:
my t (first)
for v (rest):
t (t + v)
return t
say sum(1, 2, 3)
>> 6
The nested call glues its parens: a bare argument list belongs to the
statement head alone — sum 1, 2, 3 on its own line is fine, but inside
another statement's arguments the parens say where the inner list ends.
A complete little program
Can you read this? It defines a classifier with a {verbose} option,
collects a label for each age with a value-producing loop, and reports:
.classify {verbose} age {int}:
say "checking {age}" if verbose?
return (choose:
age < 18 "minor"
age < 21 "US minor"
default "adult"
)
my ages ([12, 19, 30])
my labels (for {list} age (ages):
classify {verbose} age
)
say "{labels.count} people: {labels.join(', ')}"
>> checking 12
>> checking 19
>> checking 30
>> 3 people: minor, US minor, adult
{verbose}in braces is an option — braces after a head always mean named options, at definitions and at call sites alike. Inside the body, the option is an ordinary variable;verbose?asks whether it was set.age {int}declares a typed parameter.for {list}is a loop used as a value: it collects each iteration's result into a list.- Everything — keyword, definition, call — has the same statement shape:
head {options} arguments (value), then:and an indented block. Learn the shape once and you have read the whole language.