Chapter 6
Definitions
Overview
Definitions declare new types that can be used throughout a program. Types can have properties, methods, and nested definitions.
Functions, Procedures, and Operators
A routine is introduced by one of three keywords, and that keyword — not the spelling of the name — decides how the routine behaves:
function declares a pure function: its inputs are borrowed and immutable, and it returns a result without side effects. A function is applied prefix, by writing its name before a parenthesized argument list.
procedure declares a routine that may mutate its inputs and have side effects (builders, I/O).
operator declares an operator: a pure routine applied infix between its two operands (or prefix, for a unary operator).
The three identifier forms from the lexical chapter — alphanumeric, operator-character, and single-quoted string identifiers — are interchangeable as names here. A function may be named with operator characters, and an operator may be named with letters or a quoted string; what makes one a function and the other an operator is the declaration keyword, never the first character of the name.
function 'add seven'(n: int) returns int ; string-named function
n + 7
function ++(n: int) returns int ; operator-char-named function
n + 1
let a 'add seven'(35) ; applied prefix -> 42
let b ++(41) ; applied prefix -> 42
let c ++ 41 ; one argument needs no parentheses -> 42
let c ++ 41 ; one argument needs no parentheses -> 42
Operators may be declared at the top level, not only as members of a type. Like functions, they accept any of the three name forms:
operator <+>(left: int, right: int) returns int ; operator-char name
left + right
operator 'divided by'(left: int, right: int) returns int ; quoted name
left / right
let c 40 <+> 2 ; infix -> 42
let d 84 'divided by' 2 ; infix -> 42
Built-in arithmetic, comparison, and logical operators carry their
usual precedences. A custom operator that no level of the operation
names — a text or single-quoted name, or a symbol such as
== — binds most weakly of all, on a level of its own
below ||, so the built-in arithmetic in an expression
combines before it. For
example, given an operator 'minus' that subtracts its
right operand from its left,
20 'minus' 3 + 5 parses as
20 'minus' (3 + 5) and yields 12.
An operator's right parameter may be declared lazy:
the caller's operand is handed over unevaluated, and the operator's body
is put in where the operator stands. The standard library declares
and and or on
bool this way, so the right operand runs only where
the left one does not decide:
operator and(left: bool, right: lazy bool) returns bool
if left: right else false
let ok p <> null and p.x = 7 ; p.x is read only where p is there
The right operand is planned where the body puts it, so a null test
on the left proves its name for it, as an if proves it
for its branch. A free function and a method may declare a lazy
parameter too: the call is the body put in where it stands, ordinary
arguments evaluated once in the order they are written, a lazy one
where the body names it:
function or_else(v: int, fallback: lazy int) returns int
if v <> 0: v else fallback
let n or_else(cached, expensive()) ; expensive() runs only where cached is 0
Definitions
Definitions declare new types that can be used throughout a program.
Structure Definitions
The define keyword creates a new structure type with named properties.
| Input | Result |
|---|---|
| {"_type":"Point","x":3,"y":4}
|
| 3
|
| 7
|
| 10
|
Union Definitions
Unions define types with multiple variants. Each variant can optionally carry a value.
| Input | Result |
|---|---|
| {"_type":"Option","_variant":"Some","value":5}
|
| {"_type":"Option","_variant":"None"}
|
| {"_type":"Result","_variant":"Ok","value":42}
|
| {"_type":"Result","_variant":"Err","value":"error"}
|
| 5
|
Function Names
A routine's role is set by its declaration keyword, not by how its name is spelled. A function may be named with ordinary letters, with a single-quoted string identifier (which may contain spaces), or with operator characters; in every case it is an ordinary function, applied by writing its name before a parenthesized argument.
| Input | Result |
|---|---|
| 42
|
| 42
|
Operator Definitions
The operator keyword declares an operator, applied infix between its two operands. Like functions, operators may be named with operator characters or with a text or single-quoted name. Operators may be declared at the top level, not only inside a type. An operator that no level of the operation names, a text-named one among them, binds more weakly than every level, so the arithmetic combines first.
| Input | Result |
|---|---|
| 42
|
| 42
|
| 42
|
| 12
|
Lazy Parameters
A parameter declared lazy before its type receives its argument
unevaluated: the routine's body is put in where the call stands, and
the argument runs where the body names it, and only there. This is how
the standard library declares and and or, and it works for an ordinary
function as well.
| Input | Result |
|---|---|
| 5
|
| 42
|