Expressions
An expression denotes a value. Every declaration's body is one, every argument to a function
is one, and every branch of a case is one — there is no statement form anywhere in the
language.
The forms
| Form | Written |
|---|---|
| Literal | 42, 1.5, 'x', "hello" |
| Variable | count, List.map |
| Constructor | Nothing, Maybe.Just |
| Application | f x y |
| Operator application | a + b |
| Prefix negation | -x |
| Grouping | (a) |
| Tuple | (a, b), (a, b, c) |
| Conditional | if c then a else b |
| Case analysis | case v of … |
| Local bindings | let … in … |
| Lambda | \x -> e |
Three more forms — list literals, records, and record field access — are part of the language and are specified in their own chapters: Lists for the first and Records for the other two. None of the three is implemented in full; see Forms the compiler does not have at the foot of this chapter.
Literals
A literal is an expression whose value is written out in full. The spelling of each is Lexical structure' subject.
A literal's type is its spelling
A numeric literal written without a point is an Int. One written with a point is a
Float.
module Example exposing ()
count = 1
ratio = 1.5
letter = 'x'An annotation cannot change that: a declaration annotated Float whose body is 1 is an
error, and the fix is to write 1.0. Arithmetic between the two is an error for the same
reason.
module Example exposing (ratio)
ratio : Float
ratio = 1Because a literal carries no constraint, nothing in the language defaults: there is no fallback rule, and no class the compiler has to know by name.
The cost lands in one place: inside a function constrained over a numeric class, a literal is already concrete, so it cannot be used at the constrained type:
module Example exposing (double)
double : Number a => a -> a
double x =
mul x 22 is an Int, so mul x 2 would make a an Int, and the annotation promises every a
that has a Number instance. double x = add x x is the way to write it, and a class that
wants numeric constants declares them as members.
There is no boolean literal
Bool is an ordinary union type and True and False are its constructors, resolved and
shadowed like any other name — see Reserved words.
String literals
A string literal is an expression of type String; see
Lexical structure for what it may contain.
module Example exposing ()
greeting = "hello"Names
A variable is a lowercase-initial name, optionally qualified by a module. A constructor is an uppercase-initial one, likewise qualifiable. Which module a name is looked up in, and what makes one ambiguous, is Name resolution and scoping' subject.
module Example exposing ()
type Shape
= Square
| Circle
area shape =
shape
corners = SquareA constructor is an ordinary function of its arguments, so it applies and partially applies like one:
module Example exposing ()
type Box a
= Box a
boxed = Box 1
wrap = Boxwrap here is the constructor itself, not applied to anything — a function from a to
Box a.
Application
A function is applied to an argument by writing them next to each other. Application is
left-associative, so f x y is (f x) y: f is applied to x, and the result is
applied to y. Every function takes exactly one argument and a multi-argument function is one
that returns a function, which is why partial application needs no syntax of its own.
module Example exposing ()
apply f x =
f x
twice f x =
f (f x)Application binds tighter than every other form. An argument is therefore an atomic
expression — a literal, a name, a parenthesised expression, or a tuple — and anything else has
to be parenthesised. f (f x) above needs its parentheses: f f x would be (f f) x.
The same rule is why if and case need parentheses in argument position. g if c then a else b cannot be read as applying g to a conditional, because g claims if as its
argument before the conditional is assembled, and if is not an atomic expression:
module Example exposing (Flag, f)
type Flag
= On
| Off
f g c = g if c then On else Offmodule Example exposing ()
type Flag
= On
| Off
f g c = g (if c then On else Off)Nothing in the grammar restricts what may be applied. Applying a value that is not a function is a type error rather than a syntax error:
module Example exposing ()
f = 1 2Grouping and tuples
Parentheses around a single expression group: (a) is a, and no node exists for the
parentheses. Parentheses around two or three comma-separated expressions build a tuple.
module Example exposing ()
pair a b = (a, b)
triple a b c = (a, b, c)
nested a b = ((a, b), b)A tuple has two or three elements only — see Tuple types, which carries the reasoning. Four elements is a syntax error:
module Example exposing (f)
f a b = (a, b, a, b)Operators
An operator is a name
An operator has no meaning of its own. It is a name, bound by an
infix declaration to an ordinary function, and a + b is
that function applied to a and b. Nothing about arithmetic, comparison or composition is
built into the language: std/core declares every operator it offers in exactly the way a
program declares its own.
module Example exposing ()
infix left 6 (+) = add
add a b =
a
sum a b c =
a + b + cAn operator with no infix declaration in scope is an unresolved name, and the diagnostic
says so under the operator itself:
module Example exposing (f)
f a b =
a + bNaming an operator
An operator wrapped in parentheses is an expression denoting the function it is bound to. It
is the same spelling an infix declaration and an exposing list use, so a
module that exports (+) exports something an expression can name.
module Example exposing ((+), add, plus)
infix left 6 (+) = add
add a b =
a
plus = (+)Not implemented: (+) in expression position is a parse error, so an exported operator
can be used infix and in no other way — it cannot be passed to a higher-order function at all.
LANG-23 is the ticket.
Precedence and associativity
Each infix declaration gives its operator a precedence and an associativity, and
those two decide how an expression holding more than one operator groups. A higher precedence
binds tighter, so a * b + c is (a * b) + c when * is declared above +. Among operators
of equal precedence, left groups leftward and right groups rightward: a - b - c is
(a - b) - c, and a ++ b ++ c is a ++ (b ++ c).
Grouping is decided by these declarations alone. It does not depend on spacing, on the order the declarations appear in, or on anything the operators are bound to.
module Example exposing ()
infix left 6 (+) = add
infix left 7 (*) = mul
add a b =
a
mul a b =
a
poly a b c =
a * b + cEqual precedence, disagreeing associativity
Two operators of the same precedence group an expression only when they agree how — both
left or both right. Anything else has no unambiguous grouping, and an expression that
mixes the two without parentheses is rejected. A left against a right disagrees. An
operator declared non agrees with nothing at all, itself included:
module Example exposing ((==), eq, chain)
infix non 4 (==) = eq
eq a b =
a
chain a b c =
a == b == cFalling back to left-grouping would let a == b == c compile as (a == b) == c, comparing a
boolean against c.
Two different non operators of the same precedence are rejected for the same reason. They
do not disagree — neither offers to group the other — and there is no grouping left to fall
back on:
module Example exposing ((<), (>), lt, gt, chain)
infix non 4 (<) = lt
infix non 4 (>) = gt
lt a b =
a
gt a b =
a
chain a b c =
a < b > cPrefix negation
A - with no left operand available is prefix negation rather than subtraction. That rule
has no exceptions: it reads off the text alone and never off the spacing.
At the start of an expression there is no left operand, so - negates:
module Example exposing ()
infix left 6 (-) = sub
sub a b =
a
opposite n =
-nDirectly after a binary operator there is no left operand either, so - negates there too:
module Example exposing ((-), (+), sub, add, f)
infix left 6 (-) = sub
infix left 6 (+) = add
sub a b =
a
add a b =
a
f a b =
a - -bKnown gap: that should be expect=ok — a - -b is a minus the negation of b. The
right operand of an operator may not be a negation today, and a - (-b) is the only way to
write it. LANG-22 is the ticket.
After an expression there is a left operand, so - is subtraction — including in what looks
like argument position. g -n is g - n, not g applied to the negation of n:
module Example exposing ()
infix left 6 (-) = sub
sub a b =
a
f g n =
g -nReading g -n as an application would make meaning turn on spacing alone. Write g (-n) when
an argument is what is meant.
Negation takes exactly the operand in front of it, and binds tighter than every operator
however that operator is declared. -a + b is (-a) + b and -a * b is (-a) * b; a
negation that is meant to cover more is written with parentheses, as -(a + b).
module Example exposing ()
infix left 6 (-) = sub
infix left 6 (+) = add
sub a b =
a
add a b =
a
f a b =
-a + bThat block pins the syntax only — both groupings type the same, and nothing here evaluates
anything. crates/zelkova-compiler/tests/canonical.rs is where the grouping itself is pinned.
There is likewise no negative literal: -1 is negation applied to the literal 1. A
pattern carries its sign on the literal instead, because
pattern syntax is closed and never looks an operator up.
if … then … else
if c then a else b evaluates c, which must be a Bool, and has the value of a or of
b. The else is not optional — an if with no value for one of its cases could not be
an expression.
module Example exposing (Flag, f)
type Flag
= On
| Off
f c = if c then OnBoth arms are ordinary expressions, so an else if chain is nesting rather than a form of its
own:
module Example exposing ()
type Flag
= On
| Off
classify a b =
if a then
On
else if b then
Off
else
OnAn if may appear wherever an expression may, subject to the
application rule above: parenthesised in argument position, bare elsewhere.
module Example exposing ()
type Flag
= On
| Off
f c = (if c then On else Off, Off)
g c =
case if c then On else Off of
On ->
Off
Off ->
OnAn if extends rightward as far as it can. Used as an operator's right operand it swallows
everything after it, so 1 + if c then a else b is 1 + (if c then a else b); by the same
rule, an if on the left of an operator swallows the operator too. if c then 1 else 2 + 3
is if c then 1 else (2 + 3), so parentheses are the only way to make an if a left operand.
module Example exposing ((+), add, f)
infix left 6 (+) = add
add a b =
a
f c =
1 + if c then 2 else 3Known gap: that should be expect=ok. An operator's right operand may not be an if
today. LANG-22 is the ticket.
case … of
case v of matches v against a list of branches, each a pattern, ->, and an expression.
The branches are tried in the order written and the first match wins; between them they must
cover the type of v. Every branch has the same type, which is the type of the whole
expression.
What a pattern may be, and what binding one means, is Patterns' subject. Where the branches must sit on the page is Layout's.
module Example exposing ()
type Flag
= On
| Off
invert flag =
case flag of
On ->
Off
Off ->
OnA case has at least one branch. A case with none could not have a value:
module Example exposing (f)
f v =
case v ofA case is an expression like any other, so it nests in both positions — as the scrutinee of
another case, and as a branch body:
module Example exposing ()
type Flag
= On
| Off
both a b =
case a of
On ->
case b of
On ->
On
Off ->
Off
Off ->
OffThe same is true of parenthesisation, so a case may also appear as an argument, a tuple
element, or an operand:
module Example exposing (Flag, f)
type Flag
= On
| Off
f g v =
g (case v of
On ->
Off
Off ->
On)Known gap: that should be expect=ok. A case cannot be parenthesised at all today, in
any position — the closing ) arrives while the branch block is still open — so a case is
usable only as a whole declaration body, a whole if arm, or a whole branch body. It cannot be
an argument, a tuple element, an operator operand, or another case's scrutinee.
LANG-21 is the ticket.
A case in the then arm of an if ends where the else begins, the same as any other
block that a following keyword closes:
module Example exposing ()
type Flag
= On
| Off
f c v =
if c then
case v of
On ->
Off
Off ->
On
else
Onlet … in
let bindings in expression introduces names visible in the bindings themselves and in the
expression after in. The value of the whole is the value of that expression.
module Example exposing (f)
f n =
let
double x =
mul 2 x
(lo, hi) =
bounds n
in
double (add lo hi)Not implemented: let and in are reserved words with no production behind them, and
everything below is design intent. Where the bindings sit on the page is
Layout's subject, with two open questions of its own. A let binding
takes any form a module's declarations take — a type annotation,
a value binding, a function binding with parameters, and a
destructuring pattern, which must be irrefutable
since there is nowhere for a failed match to go. The bindings of one let are mutually
recursive: every one of them is in scope in every other's body, and in the expression after
in, so the order they are written in does not matter and two functions may call each other.
A let binds nothing outside itself. It is the only construct that introduces a name that is
not a declaration or a pattern.
Lambdas
\patterns -> expression is an anonymous function. It takes one or more parameters, and
\x y -> e means the same as \x -> \y -> e, matching the way
application and the function arrow already
associate.
module Example exposing (f)
f =
\x y -> add x yNot implemented: there is no lambda production — \ is an ordinary operator character, so
\x -> x is rejected as an operator in a position where an expression was wanted. Its
parameters are meant to be the patterns a function declaration's parameters accept, for the
same reason: a lambda has one body, so a pattern that can fail has nowhere to fall through to.
Only irrefutable patterns may
appear.
module Example exposing (Point, f)
type Point a
= Point a a
f =
\(Point x y) -> add x yA lambda's body extends as far to the right as it can, so \x -> f x y is \x -> (f x y) and
a lambda used as an argument is parenthesised like any other non-atomic expression. A lambda
cannot span multiple lines.
Forms the compiler does not have
Three expression forms are part of the language and are specified elsewhere:
| Form | Where it is specified |
|---|---|
List literals — [1, 2] | Lists |
Records — { a = 1 }, { r | a = 2 } | Records |
Field access — r.name, .name | Records |
module Example exposing (f)
f : { name : Int } -> Int
f r =
r.name