Operators

Every operator the language has, listed loosest-binding first and tightest-binding last - higher precedence binds tighter, so an operator further down this page grabs its operands before one further up does. The set is fixed: a program can't declare operators of its own. Your own types get the builtin ones by implementing an interface - Numeric for + and *, Comparable for <, and so on - see Interfaces.

Fixity is where the operator sits relative to its operand(s): infix sits between two (a + b), prefix before one (-a), postfix after one (a!!), and chaining is a comparison-style infix that chains with others at the same tier into one Operator Chain (a < b < c) instead of nesting. Associativity only matters for infix operators, and says which side a repeated chain groups from: left means a - b - c reads as (a - b) - c, right means a ^ b ^ c reads as a ^ (b ^ c).

Some operators have another spelling too - = for ==, and Unicode ones like ≠ for != and ∧ for and - shown under the main one on its card. It's the same operator, at the same precedence.

Cards tagged grammar - call and index, and their guarded ?/!/!? forms - are part of the grammar itself rather than the operator table.

Assignment

Assignment is a statement, not an operator - it has no precedence and only appears after a set target (see Set). set a <op>= b means set a := a <op> b, for every infix and chaining operator and their other spellings: +=, +|=, ??=, |>=, and=, shl=, ≠=, or a custom one like ++=. A word operator needs its = right against it - and=, not and =. Comparisons are no exception, so set a <= b is set a := a < b and set a <== b is set a := a <= b. The one exception is equality: == and != are the equality operators (= being the usual spelling of ==), never = or ! then assign, so their compounds are === and !==. The target of a set is never itself a binary operation, which is what lets set a <= b read this way. := has a Unicode spelling too, ≔ - written anywhere := is, not just after set.

Undefined and errors

A value of type T? may be undefined, and one of type E ! T may be an error; E ! T? may be either. Seven operators handle them, in pairs - ? for undefined, ! for an error - and each handles only its own side, passing the other on untouched. See Errors for when to use which.

OperatorWhen the left side is ...... it givesOtherwise
a ?? bundefinedba, an error too
a !? ban errorba, undefined too
a ?! bundefinedan error carrying ba
a ?> fundefinedwhat f() gives - a value or an errora
a !> fan errorf(error, value)'s outcomea
a ?|> fneitherf(a)a - an undefined or error, as it is
a !|> fnot an errorf(a)a - the error, as it is

The right side of ?? and !? is only worked out when it's used, so find(id) ?? expensiveDefault() calls expensiveDefault only when nothing was found. When a value can be both, write both fallbacks - in either order, each takes away its own side:

Running a function: ?> and !>

?> calls a function of no arguments when the left side is undefined, and gives what it gives: a value to use instead, or an error to fail with. !> is the same for an error: the function gets the error and the value attached to it with error ... with (undefined when there's none). A λ with a block, λ(e, value) { ... }, gives the whole outcome - return a value to recover, error to fail. Any other function - λ(e) e.wrap(...), or a function's name - gives the new error, so the result still fails. Both are written with a space before them: Array<String?> closes its generics, while a ?> f is the operator.

Piping: ?|> and !|>

Like |>, these pass the value to a function, but only when there's a value to pass: ?|> skips an undefined and an error, !|> only an error, and what's skipped is the result as it is. A step can fail itself, and then the steps after it are skipped too.

Precedence

All seven share one precedence, below arithmetic, ranges and comparisons and above and, or and |>, and they group left to right.

What the checker reports

Using one where the type says only the other can happen is an error: ?? on a value that can't be undefined but can fail, and !? on one that can't fail but can be undefined. Either way the operator would never do anything, and the error - or the undefined - would pass on unhandled.

Guarded access

Member access, calls and indexing each have error-aware forms. With ! - !., !(), ![] - they assert the value isn't an error; if it is, the program stops there. With !? - !?., !?(), !?[] - they skip the access on an error or undefined, giving undefined. The ? forms, ?., ?() and ?[], do the same for undefined only.

Pipe (5)

|>

#infix-pipe-gt
infixprecedence 5left-associative

Applies the left side as an argument to the right side's function, e.g. x |> f calls f(x). Left-associative, so x |> f |> g applies f then g to the result, left to right.

||>

#infix-pipe-pipe-gt
infixprecedence 5left-associative

Map-pipe: |> applied to each element. Calls the right side's function once per element of the Array (or any other iterable) on the left, and collects the results into a new Array - [1, 2, 3] ||> double is [2, 4, 6]. Shares |>'s tier, so xs ||> f |> g maps f over xs, then passes the whole new Array to g.

Composition (7)

∘

#infix-∘
infixprecedence 7left-associative

Function composition, read as "after": (f ∘ g)(x) is f(g(x)) - apply g, then f to its result. For left-to-right order, pipe instead: x |> g |> f.

Logical Iff (8)

iff

#infix-iff
infixprecedence 8left-associative

Biconditional ("if and only if") - the loosest logical connective, so it relates two whole propositions: a and b iff c or d reads as (a and b) iff (c or d). Backed by Logical.

Logical Or (10)

or

#infix-or
infixprecedence 10left-associative

Logical or, backed by Logical.

Logical Xor (15)

xor

#infix-xor
infixprecedence 15left-associative

Logical exclusive-or - tighter than or, looser than and, the usual ordering when the three sit alongside each other. Backed by Logical.

Logical And (20)

and

#infix-and
infixprecedence 20left-associative

Logical and, backed by Logical.

Error Definition (25)

!

#infix-bang
infixprecedence 25right-associative

Error definition - an error union type: the left side is the error, the right side the value, e.g. ParseError ! Number is either a ParseError or a Number. Right-associative, so A ! B ! Number reads as A ! (B ! Number).

Undefined and Error (27)

??

#infix-question-question
infixprecedence 27left-associative

Nil-coalescing: evaluates to the right side (a plain value, never called) when the left side is undefined, or to the left side unchanged otherwise. An error passes through - that's what !? is for. Left-associative, so a ?? b !? c handles an undefined, then an error.

!?

#infix-bang-question
infixprecedence 27left-associative

Error fallback, the counterpart of ??: evaluates to the right side (a plain value, never called) when the left side errored, or to the left side unchanged otherwise. parse(text) !? 0.

?>

#infix-question-gt
infixprecedence 27left-associative

Calls the right side, a function of no arguments, when the left side is undefined, and evaluates to what it gives - a value, or an error to fail with: find(id) ?> λ { error "no such user" }. Otherwise the left side, unchanged. Written with a space before it, so Array<String?> still closes its generics.

!>

#infix-bang-gt
infixprecedence 27left-associative

Maps an error: when the left side errored, calls the right side with the error and the value it carries (error e with value). A λ with a block gives the whole outcome - a value, error, or error ... with - and any other function just the new error: load(path) !> λ(e) e.wrap("couldn't load the config"). Anything that isn't an error passes through. Written with a space before it.

?|>

#infix-question-pipe-gt
infixprecedence 27left-associative

Pipes a value: x ?|> f is f(x) when x has a value, and an undefined or an error passes through untouched - find(id) ?|> render. What f gives is the result, an error too. In this tier, not |>'s, so a chain ends with its fallback: find(id) ?|> render ?? "none".

!|>

#infix-bang-pipe-gt
infixprecedence 27left-associative

Pipes past errors: x !|> f is f(x) unless x errored, and the error passes through untouched. What f gives is the result, so a step that fails stops the rest: read(path) !|> parse !|> check !? fallback.

?!

#infix-question-bang
infixprecedence 27left-associative

Undefined-to-error, the reverse of ??: when the left side has no value, evaluates to an error carrying the right side (a plain value, never called); otherwise to the left side unchanged. An already-errored left side keeps its own error. Pairs with !! to bail out on a missing value: (find(key) ?! "not found")!!.

Comparison (50)

==

#chaining-eq-eq
chainingprecedence 50

Equal to - also spelled =, since setting a value is always :=. All six comparisons share one precedence tier so they chain as an Operator Chain, e.g. a < b <= c != d checks every comparison at once.

!=

#chaining-bang-eq
chainingprecedence 50

Not equal to.

<

#chaining-lt
chainingprecedence 50

Less than.

<=

#chaining-lt-eq
chainingprecedence 50

Less than or equal to.

>

#chaining-gt
chainingprecedence 50

Greater than.

>=

#chaining-gt-eq
chainingprecedence 50

Greater than or equal to.

in

#infix-in
infixprecedence 50left-associative

Membership: whether the right side contains the left side, e.g. x in xs.

Any Of (51)

|

#chaining-pipe
chainingprecedence 51

Value alternation - x == a | b groups as x == (a | b) ("x equals a or b"), not (x == a) | b. Checked as "both sides the same type", for any type at all - not bitwise despite the symbol.

All Of (53)

&

#chaining-amp
chainingprecedence 53

Value combination, the "and" counterpart to |, binding tighter than it the same way and binds tighter than or. Note: a&b with no surrounding space lexes as a Context Reference instead, not this operator.

Cast (55)

is

#infix-is
infixprecedence 55left-associative

Type check: whether a value is of the given type.

as

#infix-as
infixprecedence 55left-associative

Type cast: reinterprets a value as the given type.

Range (57)

..<

#infix-dot-dot-lt
infixprecedence 57left-associative

A half-open range from the left side up to, but not including, the right side: a..<b is [a, b), e.g. 0..<r.length.

..=

#infix-dot-dot-eq
infixprecedence 57left-associative

A closed range, including the right side: a..=b is [a, b].

Bitwise Or (60)

bor

#infix-bor
infixprecedence 60left-associative

Bitwise or, backed by Bitwise. A word rather than |, which is value alternation.

bxor

#infix-bxor
infixprecedence 60left-associative

Bitwise exclusive-or, backed by Bitwise. A word rather than ^, which is exponentiation.

Bitwise And (62)

band

#infix-band
infixprecedence 62left-associative

Bitwise and, backed by Bitwise. A word rather than &, which is value combination.

Shift (65)

shl

#infix-shl
infixprecedence 65left-associative

Shift left, backed by Bitwise.

shr

#infix-shr
infixprecedence 65left-associative

Shift right, backed by Bitwise - arithmetic for signed integers, logical for unsigned.

Additive (70)

+

#infix-plus
infixprecedence 70left-associative

Addition - an integer result that doesn't fit its type is an OverflowError.

-

#infix-minus
infixprecedence 70left-associative

Subtraction - an integer result that doesn't fit its type is an OverflowError.

++

#infix-plus-plus
infixprecedence 70left-associative

Concatenation: the left side's contents, then the right side's - "ab" ++ "c" is "abc", [1] ++ [2, 3] is [1, 2, 3], (1,) ++ (2, 3) is (1, 2, 3). Works on text, Bytes, every collection (for a Set it's the union, for a Map the right side's value wins) and tuples, and on any type of your own through Concatenable. Shares +'s tier, so "n = " ++ n + 1 needs parentheses around n + 1; set xs ++= ys appends ys to xs.

+%

#infix-plus-percent
infixprecedence 70left-associative

Wrapping addition: a sum that doesn't fit wraps around, keeping the low bits, where + would be an OverflowError - (250 as U8) +% 10 is 4 - a +% b is a.wrappingAdd(b). Integers only, both sides of one type; see Numbers and Overflow.

-%

#infix-minus-percent
infixprecedence 70left-associative

Wrapping subtraction: a difference that doesn't fit wraps around - (3 as U8) -% 5 is 254 - a -% b is a.wrappingSubtract(b). Integers only, both sides of one type; see Numbers and Overflow.

+|

#infix-plus-pipe
infixprecedence 70left-associative

Filling addition: a sum that doesn't fit stops at the type's largest or smallest value - (250 as U8) +| 10 is 255 - a +| b is a.saturatingAdd(b). Integers only, both sides of one type; see Numbers and Overflow.

-|

#infix-minus-pipe
infixprecedence 70left-associative

Filling subtraction: a difference that doesn't fit stops at the type's smallest or largest value - (3 as U8) -| 5 is 0 - a -| b is a.saturatingSubtract(b). Integers only, both sides of one type; see Numbers and Overflow.

Multiplicative (80)

*

#infix-star
infixprecedence 80left-associative

Multiplication - an integer result that doesn't fit its type is an OverflowError.

*%

#infix-star-percent
infixprecedence 80left-associative

Wrapping multiplication: a product that doesn't fit wraps around, keeping the low bits - (250 as U8) *% 10 is 196 - a *% b is a.wrappingMultiply(b). Integers only, both sides of one type; see Numbers and Overflow.

*|

#infix-star-pipe
infixprecedence 80left-associative

Filling multiplication: a product that doesn't fit stops at the type's largest or smallest value - (250 as U8) *| 10 is 255 - a *| b is a.saturatingMultiply(b). Integers only, both sides of one type; see Numbers and Overflow.

/

#infix-slash
infixprecedence 80left-associative

Division.

//

#infix-slash-slash
infixprecedence 80left-associative

Floor division.

%

#infix-percent
infixprecedence 80left-associative

Remainder.

Exponent (90)

^

#infix-caret
infixprecedence 90right-associative

Exponentiation, right-associative so a ^ b ^ c reads as a ^ (b ^ c). Also reachable through the compact a² superscript form - see Binary Operation.

Unary (100)

-

#prefix-minus
prefixprecedence 100

Negation.

+

#prefix-plus
prefixprecedence 100

Unary plus.

not

#prefix-not
prefixprecedence 100

Logical not, backed by Logical.

~

#prefix-tilde
prefixprecedence 100

Bitwise not, backed by Bitwise.

..

#prefix-dot-dot
prefixprecedence 100

Spread: expands an iterable's own elements in place, e.g. [..a, 1, ..b].

Postfix (110)

?

#postfix-question
postfixprecedence 110

Marks a type as possibly absent, e.g. field name: String? - shorthand for Optional, String | Undefined.

!!

#postfix-bang-bang
postfixprecedence 110

Error bubbling: value!! is value unchanged unless it's errored, in which case the enclosing function immediately exits with that same errored value, the way an Error statement would. Not allowed inside a λ.

Await (115)

await

#prefix-await
prefixprecedence 115

Pauses the @async function it's in until a Promise settles, and gives the promise's value - other tasks run meanwhile. Only allowed inside an @async body. Its tier sits between Postfix and Member, so it takes a whole call but not the !! after it: await load(path)!! is (await load(path))!!, which bubbles the error the settled value carries.

Member (120)

.

#infix-dot
infixprecedence 120left-associative

Member access, e.g. a.b. Left-associative, so a.b.c reads as (a.b).c - the tightest-binding operator there is.

?.

#infix-question-dot
infixprecedence 120left-associative

Short-circuiting member access: skips the access, instead of erroring, when the object side is undefined.

!.

#infix-bang-dot
infixprecedence 120left-associative

Asserting member access: accesses the member, but first asserts that the object side isn't itself an error.

!?.

#infix-bang-question-dot
infixprecedence 120left-associative

Short-circuiting member access that also covers errors: skips the access when the object side is an error or undefined.

()

#postfix-call
postfixprecedence 120grammar

Call: f(x). Part of the grammar rather than the operator table - the ( must sit directly against the callee.

?()

#postfix-question-call
postfixprecedence 120grammar

Short-circuiting call: f?(x) skips the call, instead of erroring, when the callee is undefined - the result is undefined. The call counterpart of ?..

!()

#postfix-bang-call
postfixprecedence 120grammar

Asserting call: f!(x) calls f, but first asserts that the callee isn't itself an error. The call counterpart of !..

!?()

#postfix-bang-question-call
postfixprecedence 120grammar

Short-circuiting call that also covers errors: f!?(x) skips the call when the callee is an error or undefined. The call counterpart of !?..

[]

#postfix-index
postfixprecedence 120grammar

Index: xs[i], backed by Indexable. Part of the grammar rather than the operator table - the [ must sit directly against the indexed value.

?[]

#postfix-question-index
postfixprecedence 120grammar

Short-circuiting index: xs?[i] skips the lookup, instead of erroring, when the indexed value is undefined - the result is undefined. The index counterpart of ?..

![]

#postfix-bang-index
postfixprecedence 120grammar

Asserting index: xs![i] looks up i, but first asserts that the indexed value isn't itself an error. The index counterpart of !..

!?[]

#postfix-bang-question-index
postfixprecedence 120grammar

Short-circuiting index that also covers errors: xs!?[i] skips the lookup when the indexed value is an error or undefined. The index counterpart of !?..