Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Operator Precedence

How Suji groups an expression, from the loosest binding to the tightest.

The Table

Level 1 binds loosest, level 17 tightest. Operators on the same level are applied in the order given by their associativity.

LevelOperatorsAssociativityNotes
1=, destructuring a, b = …righta = b = 3 assigns 3 to both
2+= -= *= /= %=rightCompound assignment
3<|rightBackward pipe apply
4|>leftForward pipe apply
5|leftStream pipeline (shell and closures)
6>> <<leftFunction composition
7||leftBoolean or
8&&leftBoolean and
9~ !~leftRegex match / not-match
10== !=leftEquality
11< <= > >=leftRelational
12.. ..=Ranges do not chain
13+ -leftAddition, subtraction, concatenation
14* / %leftMultiplication, division, remainder
15unary - !prefixBinds looser than ^
16^rightExponentiation
17() [] :: : ++ --postfixCall, index, method, map key, increment

Surprises Worth Memorising

^ is right-associative and needs an integer exponent

import std:println

println(2 ^ 3 ^ 2)   # 512
println(2 ^ 10)      # 1024

2 ^ 3 ^ 2 groups as 2 ^ (3 ^ 2), which is 2 ^ 9. A non-integer exponent such as 4 ^ 0.5 is a runtime error; use 16::sqrt() for roots.

Unary minus binds looser than ^

import std:println

println(-2 ^ 2)     # -4
println((-2) ^ 2)   # 4

-2 ^ 2 is -(2 ^ 2), not (-2) ^ 2.

Ranges sit between comparison and +

Arithmetic inside a range endpoint needs no parentheses, but a comparison against a range does.

import std:println

println(1 .. 3 + 1)   # [1, 2, 3]

1 .. 3 + 1 groups as 1 .. (3 + 1), producing the exclusive range 1..4.

~ binds looser than ==

This is the opposite of what most people expect. "abc" ~ /b/ == true groups as "abc" ~ (/b/ == true) and fails with a type error. Parenthesise the match:

import std:println

println(("abc" ~ /b/) == true)   # true

In practice you rarely need this, because ~ already yields a boolean.

There are three distinct pipe operators

OperatorLevelWhat it does
|>4Applies the left value to the right function
<|3Applies the right value to the left function
|5Connects stdout of one stage to stdin of the next

Because \| binds tighter than both apply operators, a shell pipeline is built first and its result is then piped into a function.

import std:io
import std:println

count = || io:stdin::read_lines()::length()
label = |n| "lines: ${n}"

`printf 'a\nb\nc\n'` | count() |> label |> println   # lines: 3

|| versus the empty-parameter lambda

|| is boolean or, and || expr is a lambda that takes no parameters. The parser distinguishes them by position: || after a value is the operator, || where a value is expected starts a lambda.

import std:println

flag = true || false     # boolean or
zero = || 42             # lambda with no parameters

println(flag)     # true
println(zero())   # 42

Both && and || require boolean operands. nil || "default" is a type error, so there is no || idiom for defaults — use m::get(k, default) or a match.

Worked Examples

Each result below was produced by running the expression.

ExpressionGroups asResult
2 + 3 * 42 + (3 * 4)14
2 * 3 ^ 22 * (3 ^ 2)18
2 ^ 3 ^ 22 ^ (3 ^ 2)512
-2 ^ 2-(2 ^ 2)-4
10 - 3 - 1(10 - 3) - 16
1 .. 3 + 11 .. (3 + 1)[1, 2, 3]
true || false && falsetrue || (false && false)true
1 + 2 |> inc(1 + 2) |> inc4
3 |> inc >> double3 |> (inc >> double)8
double <| inc <| 3double <| (inc <| 3)8
import std:println

inc = |x| x + 1
double = |x| x * 2

println(2 + 3 * 4)              # 14
println(2 * 3 ^ 2)              # 18
println(10 - 3 - 1)             # 6
println(true || false && false) # true
println(1 + 2 |> inc)           # 4
println(3 |> inc >> double)     # 8
println(double <| inc <| 3)     # 8

3 |> inc >> double is worth a second look. Composition binds tighter than pipe apply, so inc >> double is built into a single function first and 3 is then applied to it. Had the grouping been (3 |> inc) >> double, the left operand of >> would be the number 4 and composition would fail.

double <| inc <| 3 shows the right-associativity of <|: the innermost apply inc <| 3 runs first, and its result is handed to double. Left-associative grouping would try to compose double with inc, which <| does not do.

Forcing a Grouping

Parentheses always win, and they are the cheapest way to make an expression readable when several pipe families meet in one line.

import std:println

value = ((1 + 2) * 3) ^ 2

println(value)  # 81

See Also