Special call syntax

Operators

Operators are functions, but they are parsed differently. Here is a complete list:

Unary operators

Operator Use
!x Logical "not".
x! Gets the value from an option, or throws an exception if it is empty.
-x Negates a number.
~x Bitwise negation (flips 0s and 1s in an integral value).
*x Dereferences a pointer.

Binary operators

These are in order of operator precedence.
For example, * binds more tightly than +, so 1 + 2 * 3 parses as 1 + (2 * 3).

Operator Use
~=, ~~= Mutating concatenation; adds one (~=) or many (~~=) elements to a collection.
|| Logical "or"; or for option types, chooses the first non-empty option.
This is lazily evaluated, so in a || b, if a is true (or a non-empty option), b won't be evaluated.
&& Logical "and".
This is lazily evaluated, so in a && b, if a is false, b won't be evaluated.
?? Gets a value from an option on the left, or returns the right value.
This is lazily evaluated, so in a ?? b, if a is non-empty, b won't be evaluated.
foo Ordinary named functions; included here to show precedence.
.. Numerical range, e.g. 0 .. 10
~, ~~ ~ adds one element to a collection.
For ordered collections, x ~ xs adds to x to the left and xs ~ x adds x to the right.
For unordered collections, only xs ~ x should be implemented.
~~ concatenates two collections (or combines unordered collections).
==, !=, <, >, <=, >=, <=> Comparison; <=> is 3-way comparison.
| Union of sets; or bitwise "or" for integral values.
& Intersection of sets; or bitwise "and" for integral values.
<<, >> Bitshift left and right.
+, - Addition and subtraction.
For math only; to "add" collections (including strings), use ~~.
*, /, % Multiply, divide, and modulo.
** Exponentiation; 2 ** 3 is 8.

Defining operator functions

Defining an operator looks just like defining a function.

main void() info log "a" / "b" / string(a string, b string) "{a} / {b}"

The exception is that ! would be ambiguous, so you must define a name functions not or force.

main void() a string = "crow" info log !a info log a! info log !a! not string(a string) "not {a}" force string(a string) "force {a}"

Also, the operators -, ~, and + must have explicit visibility, since the same characters are used to specify visibility.
(Visibility will be explained in Modules).

main void() "hello" + "world" # '-' is the visibility, '+' is the function - + void(a string, b string) info log "{a} + {b}"

"new" calls

We've seen () a lot in this tutorial.
This is syntax for calling a function new with no arguments.
To call it with arguments, separate them with commas. If there is only one argument, leave a trailing comma.

The following example constructs lists, which will be described in more detail in Collections.

main void() info log ().foo info log ("crow",).foo info log ("crow", "bar").foo foo string(xs string[]) ", " join xs

The parentheses can often be ommitted.

main void() xs string[] = "crow", info log (", " join xs)

Named "new"

There is a special syntax for calling new while specifying argument names.
Each argument name is written on its own line followed by :.
This is useful for creating instances of large record types. (See Records.)

main void() info log hello-world hello-world string() greeting: "Hello" target: "world" new string(greeting string, target string) "{greeting}, {target}!"

Subscript

x[y] is shorthand for x subscript y and x[y] := z is shorthand for x set-subscript y, z.

This example uses mutable lists, explained more in Collections.

main void() xs string mut[] = "crow", "bar" info log xs[0] xs[0] := "duck" info log xs[0]

You can define these by defining named functions subscript and set-subscript.

main void() info log false["k"] true["k"] := "v" subscript string(a bool, key string) "{a}[{key}]" set-subscript void(a bool, key string, value string) info log "{a}[{key}] = {value}"

Setters

Recall that x := y is used to set a local variable. The := can accept other kinds of expression on the left.

Syntax Translation Use
x := y y set-x If x is a local variable, this writes to that.
Otherwise this may set a global or thread-local variable.
x.y := z x set-y z Used to set a field in a record.
This is generated when defining a record, as explained in Records.)
x[y] := z x set-subscript y, z Used to set the value in a list at an index, or in a map at a key.
*x := y x set-deref y Used to write to a pointer.
x->y := z x set-deref-y z Used to write to a field of a record when 'x' is a pointer to the record.
This is generated for extern records.
main void() "crow".foo := "bar" true["k"] := "v" set-foo void(a string, value string) info log "{a}.foo := {value}" set-subscript void(a bool, key string, value string) info log "{a}[{key}] := {value}"

Relatedly, a function name may end with =. The idea is that x foo= y should behave the same as x := x foo y.
This example uses a lambda, described more in Lambdas.

main void() xs string mut[] = "a", "b", "c" xs filter= x => x != "b" info log "{xs.to::json}"

There's no desugaring for this; you just define the function name with an = on the end.

main void() "a" foo= "b" # Normally you wouldn't define this for an immutable type like 'string' foo= void(a string, b string) info log "{a} foo= {b}"

Line continuation

Normally a newline indicates the end of an expression, but sometimes it's clear from context that the line can't end yet.
In these contexts, the parser will skip any newlines:

  • After = or :=.
  • After a binary operator like +.
  • After the : in an assert.
  • After a comma in an argument list. But x, on its own does not continue the line since it's equivalent to x.new.
In any other context, you can still break up a long line by adding a \ before the newline.

main void() x mut nat = 0 x := 1 y nat = x + x assert y == 2 : "It's not 2?" error info log \ "It's {y}"