6. Expressions
6.1 Grammar
Expr = Provide .
Provide = Pipe { "<-" Postfix } .
Pipe = Binary { "|>" Binary } .
Binary = Unary { BinOp Unary } .
BinOp = "||" | "&&" | "==" | "!=" | "<" | "<=" | ">" | ">=" | "<." | "<=." | ">." | ">=."
| "<>" | "+" | "-" | "+." | "-." | "*" | "/" | "%" | "*." | "/." .
Unary = [ "-" | "-." ] Postfix .
Postfix = Primary { Call | "." name | "." decimal } .
Call = Data .
Primary = int_lit | float_lit | string_lit | Name | Data | List | BitArray | Block | Branches .
Name = [ name "." ] ( name | UpName ) .
Data = "(" [ DItem { "," DItem } [ "," ] ] ")" .
DItem = name "=" Expr | ".." Expr | Expr .
List = "[" [ Expr { "," Expr } [ "," ".." Expr ] [ "," ] ] "]" .
Block = "{" { Statement } Expr "}" .
Statement = Binding | Expr .
Binding = Pattern "=" Expr .
Branches = Function { "|" Function } .
Function = [ TypeParams ] Params [ "<-" SvcPat ] [ "->" Union ] Block .
Params = "(" [ PItem { "," PItem } [ "," ] ] [ "?" Expr ] ")" .
SvcPat = "(" [ SvcItem { "," SvcItem } [ "," ] ] ")" .
SvcItem = name ":" Annot | ".." name ":" Annot . TypeParams and Union are the productions of §3; PItem is that of §7.1. Every rule below decides by tokens and brackets, never by names or types, and none needs a comma.
- Function or data. A
(that begins a primary is theParamsof a function when its matching)is followed by->, or by{on the same line, or by<-and a bracket group whose matching)is followed on the same line by->or{; that group is then the service pattern. Otherwise the(beginsData, and a<-after the data is a provide (§9.2). A primary that begins with<is a function with type parameters, and its>must be followed by(. The decision is made before the contents of the brackets are read. Function bodies are always blocks. - Calls. A
Dataon the same line right after a postfix expression is a call (§1.7). Sof(x) { … }is a call and then a block. - Items. In
Data, a data type, a data pattern andParams, an item whose first two tokens are anameand=is a labelled item.==is one token, so(a == b)has one position...begins a spread or a rest. - Branches. After the
}of a function body, a|, also at the start of the next line, joins the next branch, which must begin a function ((, or the<of type parameters); anything else after it is a compile error. A|after any other expression is a compile error. A call,.for.kafter a chain applies to the whole chain. - The
->part. In the->part of a function, a{right after->,|,&or-is part of the type (§3); the first other{not inside brackets is the body. - Statements. A statement reads pattern parts while each is followed by
=. If at least one is, the statement is a binding: the parts read form its pattern, and its value begins at the first part not followed by=. Otherwise the statement is an expression. A lowercase name followed by(is never a pattern part (§7.1), soid(A) = …is an expression followed by a stray=, a compile error. - Guards.
?insideParamsbegins the guard, after the last item, and the guard ends at the matching). A?in any other pattern is a compile error.
6.2 Order of evaluation
i is strict. Expressions are worked out from left to right:
- The items of data, lists and bit arrays are worked out in the order written; a spread
..eis worked out where it is written. The order of keys never changes this order. - In a call, first the function, then the items of the argument data in the order written, then the call.
- In a binary operation, first the left side, then the right side.
&&and||do not do the right side when the left side is enough. a |> f(b)doesafirst, and then the rest in the order off(a, b).- In
e <- s,sis worked out first, in the services around it, and thenewith the services provided (§9.2).
6.3 Literals and names
The type of an integer literal is its literal type (§4.2): 1 has the type 1. When the operand of - is an integer literal, also inside groups, the result has the literal type of the negative number: -1 and -{1} have the type -1. Every other -e has the type Int, as -x and -{-1}. The type of a string literal is its literal type: "a" has the type "a". The type of a float literal is Float. The value of a name is the value it is bound to. A member of a module is module.name, or an imported name (§2.3). A constructor whose data is () is a value; any other constructor is a function from its data (§6.7).
6.4 Data
( … ) in an expression makes data (§4.1). Its items are e, the next position, counting from 0; name = e, a label; and ..e, a spread. The type of the data is the data type with the type of each item at its key. Positions come before labels, and a label may not be given twice.
- A spread
..eneeds a known type ofe(§4.8) with exactly one member, which is a data type, or a labelled type whose constructor may be used here (§2.4). Its positions come next in the numbering, and its labels are added. A labelled item written after a spread may replace a label of that spread; any other repeated key is a compile error. ( )is not a scope: the items are worked out in the scope around the data, in the order written, and a label makes no name. So(w = h, h = w)swaps the outerwandh.()is the empty data.(e)is data with one position; it is note.{ e }puts an expression in a group (§6.10).d.k, wherekis a decimal, reads positionk;d.freads the labelf(§4.8 rule 13).
6.5 Lists
The type of [e1, …, en] is List(T), where every part has the type T. T may be a union: [1, "a"] has the type List(1 | "a"), and may be used where a List(Int | String) is needed. [e1, …, en, ..rest] puts the parts in front of the list rest.
6.6 Calls
f(…) calls the function f with the data in the brackets. Every function is called in this way, whatever expression gives it. The data must have a shape that f takes (§4.6). There are no labels outside data and no default parameters: every key is given exactly once, by the data. f() passes (), and f(x) passes the one-position data (x).
6.7 Constructors and keys
C(…) makes a value of the labelled type C (§4.3). It is a call of the constructor with the data in the brackets, which must be in the data type of C. C alone is the value of a labelled type whose data is (); C() is a compile error. C(..v, label = e) is an ordinary call whose data has a spread.
v.f and v.k read a key of data, or of the data of a labelled value. Every member of the known type of v (§4.8) must have that key, and its type is the union of those parts (§4.8 rule 13). With no known type it is a compile error. Reading a key of a labelled value, and spreading it, needs the same visibility as taking it apart with a constructor pattern (§2.4).
6.8 Functions
<A, N>(p1, …) <- (x : T, …) -> R { body } is a function value. Every part but the parameter group and the body may be left out.
- The parameter group
( … )is a data pattern (§6.11). Its shape is the shape of the function. - The service pattern after
<-names the services that the function itself uses: each itemx : Tbindsxto the service whose key is the named typeT(§9.2), and the type is required. It readsAnnotafter:(§3), restricted to aNamedType; a union or function type is not a service key...env : Nbinds a row value containing exactly the services described byN, excluding the explicitly named service keys (§9.2); it is the last item, at most once, andNmust be a type parameter of the function. A service pattern has no labels, no guard and no alternatives, and is not part of the shape.<- ()is a compile error: a function that takes no service writes no<-part. - Capturing a row projects the received services to its row type. A row value retains no other services through an ambient environment or a backing pointer. Providing it restores exactly those services.
- The
->part is the type of the value the function gives. When it is left out, the value is inferred. - Type parameters
<A, N>are in scope in the whole function (§4.7). - The function may use local names from outside. The body is a block, and the value of the block is the value that the function gives back. The need of the function is inferred (§4.8 rule 4), also when the
->part is written.
6.9 Operators
Operators are part of the syntax. They are not functions, and they may not be used as values. They are in this order, from weakest to strongest. Operators at the same level go from left to right.
| Level | Operators | Operand type | Result type |
|---|---|---|---|
| 0 | <- | See below | |
| 1 | |> | See below | |
| 2 | || | Bool | Bool |
| 3 | && | Bool | Bool |
| 4 | == != | Any two types; see below | Bool |
| 5 | < <= > >= | Int | Bool |
| 5 | <. <=. >. >=. | Float | Bool |
| 6 | <> | String | String |
| 7 | + - | Int | Int |
| 7 | +. -. | Float | Float |
| 8 | * / % | Int | Int |
| 8 | *. /. | Float | Float |
The operators take values of literal types, because these are subtypes of Int and String. Their results have the whole type: 1 + 2 has the type Int, not 3, and "a" <> "b" has the type String. The one exception is - on an integer literal, also inside groups, which has a literal type (§6.3): -1 and -{1} have the type -1, but -x has the type Int.
The unary operators - (Int) and -. (Float) are stronger than all binary operators. Calls, .f, and .k are stronger still. There is no operator for “not”; the function core.not (§13) does this.
- Int and Float have different operators. One is never changed into the other without a function such as
core.to_float(§13). a |> fisf(a), passing the one-position data(a).a |> f(b, port = 1)isf(a, b, port = 1):ais put in as position 0.a |> f(..t)isf(a, ..t). The right side of|>must be a name, a call, or a chain (§6.12); anything else is a compile error that suggests a group.|>is weaker than every operator but<-, so its whole left side is its left operand:a == b |> fis{ a == b } |> f, andx |> g == 2is a compile error.e |> C, whereCis a chain, is a match (§6.12).e <- sworks outewith the servicessprovided (§9.2). Its left operand is the whole expression to its left, and its right operand is one postfix expression:a + f(x) <- (s)provides toa + f(x),x |> g <- (s)provides tox |> g, ande <- (s) + 1is a compile error. A group provides to a part:a + { f(x) <- (s) }.- When both sides of
==or!=have known types (§4.8), and these are disjoint (§4.5 rule 3), the compiler gives warning W0503, showing both types: the two sides can never be equal. The comparison is well defined and has typeBool:==is alwaysFalseand!=is alwaysTrue. A member of a known type that is_or a type variable is never disjoint from anything. Literal types count. So1 == "a",1 == 2,1 != 2,"a" == "b",x == "b"afterx = "a", and(s : String) { s == 1 }give the warning, but(s : String) { s == "a" },(m : { "a" | "b" }) { m == "b" },x == Nilwithx : { Int | Nil },[1] == [],a == bwith a type variable, and(s) { s == 1 }do not. Only the top members of the two types are compared (§4.5 rule 3), so(1, 2) == (3, 4)andSome(1) == Some(2)give no warning, though they are alwaysFalse. Literal types are not widened. The warning adds no narrowing fact. A guard? x == 2withx : 1is always false; coverage treats it as any guard (§7.4). What==does is in §11.4.
6.10 Blocks and bindings
The statements of a block are done in order. The value of a block is the value of its last expression. The last statement may not be a binding. A block with only one expression puts it in a group: { a + b } * c. A block is the only thing that makes a scope.
An expression statement that is not the last, and whose working out can do nothing but make its value, gives a compile warning: its value is not used. These are literals, names, functions, chains, and data, lists, constructor calls, .f, .k, -, and Int, Bool, String, and comparison operators made only of these. A call, |>, a provide, a block, a bit array, and a Float operator, which may stop (§11.3), do not give the warning. So assert 1 + 1 == 3, which is the two statements assert and 1 + 1 == 3 (§1.7), gives two warnings.
p = e is a binding. It works out e and matches it with the pattern p. The names in p may be used from the next statement. e is a whole expression, so u = find_user(id) <- (db) binds the result of the provide.
- The type of
emust be a subtype of the pattern type ofp(§4.8 rule 10). Sox : Int = esays thatehas the typeInt, and(a, b) = vwithv : { (Int, Int) | Nil }is a compile error; a match is used for this. - If
pcan not fail for the type ofe(§7.3), the binding needs no service. - If not, as in
Ok(v) = r, when it runs, the binding works like the matche |> (p) { … } | (_) { a.fail(AssertionFailure(message = m)) }, where the first branch goes on with the rest of the block. The binding is still typed by §4.8 rule 10, not by rule 11. Hereais theAssertservice, andmis the textpattern, then the source text ofp, thendid not match, then the text form (§11.8) of the value. SoAssertis in the need of the binding (§9.6). - A pattern is a conjunction of parts,
p1 = … = pk:a = (x, y) = ebindsetoaand to(x, y), anda = b = c = dbindsa,bandctod.
A direct value is an expression that is made without running any code: a function expression or a chain; a literal, also with - or -. before it, also when the literal is inside groups (-{1}, -.{1.5}); a name; or data with no spread, a list, a constructor call, or a group { e } whose parts are direct values. When the value of a binding is a direct value, the names of the binding may also be used inside the function bodies in that value. This is the same for every binding, local or top-level. So a local function may call itself, and one binding of data may make functions that call one another:
fact = (0) { 1 } | (n : Int) { n * fact(n - 1) }
(even, odd) = ((n) { n == 0 || odd(n - 1) }, (n) { n != 0 && even(n - 1) }) 6.11 Parameters
A function’s parameter group is a data pattern (§7). When the function is called, the argument data is matched with it as in a binding (§6.10), but its names are not generalized (§4.7): the names of the pattern may be used in the guard and the body, and a pattern that may fail reports to Assert.
- An item
pis a positional item; an itemlabel = pmatches the part underlabel.(name : T)is a positional item that bindsname; labels are given only bylabel = p. - The parameter group has no rest
..at its top: the shape of a function is fixed by its written group. Rest is allowed in nested data patterns. (port = 80)in a parameter group says that the labelportmust have the value80; there are no default parameters. Optional parts are written as branches of different shapes (§6.12).- The names of one parameter group may not be bound twice, and its labels are distinct.
- A group may end with a guard
? e(§6.12).
6.12 Branches and matching
A chain F1 | … | Fn of function expressions, with n ≥ 1, is one function value. Each branch may write its own type parameters, which are in scope in that branch only. The branches are grouped by shape: a shape group has the branches whose parameter groups have one shape, in the order written. When the chain is called with data, the group with the shape of the data is used, and in it the first branch whose parameter group matches the data and whose guard, if it has one, is True is taken; the value of its body is the value of the call. A guard is any expression of type Bool. It is worked out only after its parameter group matched, and it may use the names of the group and of the service pattern. Alternatives in an item are tried in order.
gcd = (a : Int, 0) -> Int { a } | (a : Int, b : Int) -> Int { gcd(b, a % b) }
area =
(r : Float) -> Float { 3.14159 *. r *. r }
| (w : Float, h : Float) -> Float { w *. h }
label = (opt) {
opt |>
(Some(v) ? string.is_empty(v)) { "empty" }
| (Some(v)) { v }
| (None) { "none" }
} - A group with one branch and no guard is a function of one shape: its parameter group is fitted (§4.8),
x : Tin it is a type annotation unless its item has alternatives, it is not checked for coverage, and when it may fail it reports toAssert. - Any other group is typed by §4.8 rule 11, and its branches must cover every value of its parameter type (§7.4). If not, it is a compile error. A guarded branch is not counted for coverage.
- A chain whose groups all have one shape has the type
{ B } <- N -> R, whereRis the union of its branch results. A chain with several groups has the type{ B1 | … | Bm } <- N -> R, whereBiis the parameter type of groupiandRthe union of the results of all branches. All branches of a chain share one needN, the merge of the needs of their guards and bodies and of their service patterns. Calling such a value with data of shapeSuses the group of shapeS, at run time also when the value went through data, a list or another function. e |> C, whereCis a chain, is a match, andeis its subject. It works outeand callsCwith(e), so it uses the group of shape “one position”. A match adds no call frame: the branches run in the place of the match, a call in a branch body of a match in tail position is in tail position (§11.6), and the match is not one of the calls in a stop message (§11.7). Branches of other shapes in a matched chain can never run. To match a pair, write a pattern for it in one position:p |> ((0, y)) { y } | ((x, _)) { x }.- A branch that can never be used gives a compile warning.
- A chain is a direct value (§6.10), as every function is. Where §2.5, §5, and §6.10 speak of the function bodies in a value, the guards and branch bodies of a chain count as function bodies. Each time a chain is worked out, it makes a new value, which is the same only as itself (§11.4).
core.never : <A>(Never) -> A(§13) is the match onNever:core.never(x)is correct only whenxhas no values.
Selection. A binding whose pattern is one name with no written type, and whose value is a chain or a name of a binding with a selection table, after removing groups, has a selection table: the branches of that chain, each with its shape, its written parameter pattern, its parameter type and its result type. A chain bound with a written type, put in data or a list, or passed as an argument, has no table. A call f(d), x |> f or x |> f(…) of a name f with a table is typed by the branches it selects:
- The shape of the argument data is read from the call: its positions and labels, and the keys of each spread, which need a known shape. No group of that shape is a compile error that lists the shapes
fhas. - In that group, a branch is skipped when, at some key, the known type of that item of the data is disjoint (§4.5 rule 3) from the branch’s pattern type at that key less what earlier unguarded branches of the group take there (§4.8 rule 12, applied at every place). An item with no known type is never disjoint from anything. Every branch that is not skipped is selected. When none is selected, it is a compile error that names the known types of the items.
- The call is typed as a call of the function type whose parameter is the union of the parameter types of the selected branches and whose result is the union of their results.
So with show = (n : Int) -> String { int.to_string(n) } | (s : String) -> Int { string.length(s) }, show(1) has the type String, show(x) with x : { Int | String } has the type String | Int, and h = (x) { show(x) } gives h the type (Int | String) -> String | Int. A name used where a function type is expected selects for that type (§4.8), so list.map([1, 2], show) is a List(String). Selection chooses only a type; at run time the first matching branch is always taken.