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 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.
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.
| Operator | When the left side is ... | ... it gives | Otherwise |
|---|---|---|---|
a ?? b | undefined | b | a, an error too |
a !? b | an error | b | a, undefined too |
a ?! b | undefined | an error carrying b | a |
a ?> f | undefined | what f() gives - a value or an error | a |
a !> f | an error | f(error, value)'s outcome | a |
a ?|> f | neither | f(a) | a - an undefined or error, as it is |
a !|> f | not an error | f(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:
?> 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.
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.
All seven share one precedence, below arithmetic, ranges and comparisons and above and, or and |>, and they group left to right.
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.
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.
#infix-pipe-gtApplies 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-gtMap-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.
#infix-∘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.
#infix-iffBiconditional ("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.
#infix-xorLogical exclusive-or - tighter than or, looser than and, the usual ordering when the three sit alongside each other. Backed by Logical.
#infix-bangError 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).
#infix-question-questionNil-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-questionError 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-gtCalls 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-gtMaps 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-gtPipes 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-gtPipes 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-bangUndefined-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")!!.
#chaining-eq-eqEqual 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-eqNot equal to.
#chaining-ltLess than.
#chaining-lt-eqLess than or equal to.
#chaining-gtGreater than.
#chaining-gt-eqGreater than or equal to.
#infix-inMembership: whether the right side contains the left side, e.g. x in xs.
#chaining-pipeValue 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.
#chaining-ampValue 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.
#infix-isType check: whether a value is of the given type.
#infix-asType cast: reinterprets a value as the given type.
#infix-dot-dot-ltA 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-eqA closed range, including the right side: a..=b is [a, b].
#infix-borBitwise or, backed by Bitwise. A word rather than |, which is value alternation.
#infix-bxorBitwise exclusive-or, backed by Bitwise. A word rather than ^, which is exponentiation.
#infix-bandBitwise and, backed by Bitwise. A word rather than &, which is value combination.
#infix-shrShift right, backed by Bitwise - arithmetic for signed integers, logical for unsigned.
#infix-plusAddition - an integer result that doesn't fit its type is an OverflowError.
#infix-minusSubtraction - an integer result that doesn't fit its type is an OverflowError.
#infix-plus-plusConcatenation: 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-percentWrapping 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-percentWrapping 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-pipeFilling 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-pipeFilling 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.
#infix-starMultiplication - an integer result that doesn't fit its type is an OverflowError.
#infix-star-percentWrapping 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-pipeFilling 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-slashDivision.
#infix-slash-slashFloor division.
#infix-percentRemainder.
#infix-caretExponentiation, right-associative so a ^ b ^ c reads as a ^ (b ^ c). Also reachable through the compact a² superscript form - see Binary Operation.
#prefix-minusNegation.
#prefix-plusUnary plus.
#prefix-dot-dotSpread: expands an iterable's own elements in place, e.g. [..a, 1, ..b].
#postfix-questionMarks a type as possibly absent, e.g. field name: String? - shorthand for Optional, String | Undefined.
#postfix-bang-bangError 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 λ.
#prefix-awaitPauses 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.
#infix-dotMember access, e.g. a.b. Left-associative, so a.b.c reads as (a.b).c - the tightest-binding operator there is.
#infix-question-dotShort-circuiting member access: skips the access, instead of erroring, when the object side is undefined.
#infix-bang-dotAsserting member access: accesses the member, but first asserts that the object side isn't itself an error.
#infix-bang-question-dotShort-circuiting member access that also covers errors: skips the access when the object side is an error or undefined.
#postfix-callCall: f(x). Part of the grammar rather than the operator table - the ( must sit directly against the callee.
#postfix-question-callShort-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-callAsserting call: f!(x) calls f, but first asserts that the callee isn't itself an error. The call counterpart of !..
#postfix-bang-question-callShort-circuiting call that also covers errors: f!?(x) skips the call when the callee is an error or undefined. The call counterpart of !?..
#postfix-indexIndex: xs[i], backed by Indexable. Part of the grammar rather than the operator table - the [ must sit directly against the indexed value.
#postfix-question-indexShort-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-indexAsserting 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-indexShort-circuiting index that also covers errors: xs!?[i] skips the lookup when the indexed value is an error or undefined. The index counterpart of !?..