Operators

Overview

CategoryOperators
Pipe->
Capture=>
Arithmetic+, -, *, /, %
Comparison==, !=, <, >, <=, >=
Comparison Methods.eq, .ne, .lt, .gt, .le, .ge
Logical! (unary), &&, `
Spread@ (sequential), * (tuple)
Extraction*<> (destructure), /<> (slice)
Type:type (assert), :?type (check)
Member.field, [index]
Hierarchical Dispatch[path] -> target
Default?? value
Existence.?field, .?field&type

Pipe Operator ->

The pipe operator passes the left-hand value to the right-hand side:

"hello" -> .upper              # "HELLO"
42 -> ($ + 8)                  # 50
[1, 2, 3] -> each { $ * 2 }    # [2, 4, 6]

Piped Value as $

The piped value is available as $:

"world" -> "hello {$}"         # "hello world"
5 -> ($ * $ + $)               # 30

Method Syntax

Method calls are sugar for pipes:

"hello".upper                  # equivalent: "hello" -> .upper
"hello".contains("ell")        # equivalent: "hello" -> .contains("ell")

Implicit $

Bare .method() implies $ as receiver:

"hello" -> {
  .upper -> log                # $."upper" -> log
  .len                         # $.len
}

Capture Operator =>

Captures a value into a variable:

"hello" => $greeting           # store in $greeting
42 => $count                   # store in $count

Capture and Continue

=> captures AND continues the chain:

"hello" => $a -> .upper => $b -> .len
# $a is "hello", $b is "HELLO", result is 5

See Variables for detailed scoping rules.


Arithmetic Operators

OperatorDescription
+Addition
-Subtraction
*Multiplication
/Division
%Modulo (remainder)
5 + 3                          # 8
10 - 4                         # 6
3 * 4                          # 12
15 / 3                         # 5
17 % 5                         # 2

Precedence

Standard mathematical precedence (high to low):

  1. Unary: -, !
  2. Multiplicative: *, /, %
  3. Additive: +, -
2 + 3 * 4                      # 14 (multiplication first)
(2 + 3) * 4                    # 20 (parentheses override)
-5 + 3                         # -2

Type Constraint

All operands must be numbers. No implicit conversion:

5 + 3                          # OK: 8
"5" + 1                        # ERROR: Arithmetic requires number, got string

Error Handling

10 / 0                         # ERROR: Division by zero
10 % 0                         # ERROR: Modulo by zero

Comparison Operators

OperatorDescription
==Equal
!=Not equal
<Less than
>Greater than
<=Less or equal
>=Greater or equal
5 == 5                         # true
5 != 3                         # true
3 < 5                          # true
5 > 3                          # true
5 <= 5                         # true
5 >= 3                         # true

Value Comparison

All comparisons are by value, not reference:

[1, 2, 3] == [1, 2, 3]         # true
[a: 1] == [a: 1]               # true
"hello" == "hello"             # true

Comparison Methods

Methods provide readable alternatives in conditionals:

MethodEquivalent
.eq(val)== val
.ne(val)!= val
.lt(val)< val
.gt(val)> val
.le(val)<= val
.ge(val)>= val
"A" => $v
$v -> .eq("A") ? "match" ! "no"           # "match"
5 -> .gt(3) ? "big" ! "small"             # "big"
10 -> .le(10) ? "ok" ! "over"             # "ok"

Logical Operators

OperatorDescription
&&Logical AND (short-circuit)
||Logical OR (short-circuit)
!Logical NOT
(true && false)                # false
(true || false)                # true
!true                          # false
!false                         # true

Short-Circuit Evaluation

(false && undefined_var)       # false (right side not evaluated)
(true || undefined_var)        # true (right side not evaluated)

With Comparisons

(1 < 2 && 3 > 2)               # true
(5 > 10 || 3 < 5)              # true

Grouping Required

Compound expressions require grouping in simple-body contexts:

true -> ($ && true) ? "both" ! "not both"    # "both"

Negation in Pipes

In pipe targets, !expr binds tightly and returns a boolean:

"hello" -> !.empty                 # true (not empty)
"" -> !.empty                      # false (is empty)

This works naturally with conditionals and captures:

"hello" -> !.empty ? "has content" ! "empty"   # "has content"
"hello" -> !.empty => $not_empty               # $not_empty = true

No grouping needed — !.empty is parsed as a unit before ? or =>.


Spread Operators

Sequential Spread @

Chain closures where each receives the previous result (fold pattern):

|x|($x + 1) => $inc
|x|($x * 2) => $double
|x|($x + 10) => $add10

# Chain: (5 + 1) = 6, (6 * 2) = 12, (12 + 10) = 22
5 -> @[$inc, $double, $add10]    # 22

Single closure:

|x|($x * 2) => $dbl
5 -> @$dbl                       # 10

Tuple Spread *

Create tuples for argument unpacking:

# From list (positional)
*[1, 2, 3] => $args

# From dict (named)
*[x: 1, y: 2] => $named

# Convert list to tuple via pipe
[1, 2, 3] -> * => $tuple

Using tuples at invocation:

|a, b, c|"{$a}-{$b}-{$c}" => $fmt
*[1, 2, 3] -> $fmt()             # "1-2-3"
*[c: 3, a: 1, b: 2] -> $fmt()    # "1-2-3" (named, order doesn't matter)

See Types for full tuple documentation.


Extraction Operators

Destructure *<>

Extract elements from lists or dicts into variables. Returns the original value unchanged.

List destructuring (pattern count must match list length):

[1, 2, 3] -> *<$a, $b, $c>
# $a = 1, $b = 2, $c = 3

With type annotations:

[0, "ok"] -> *<$code:number, $msg:string>
# $code = 0, $msg = "ok"

Skip elements with _:

[1, 2, 3, 4] -> *<$first, _, _, $last>
# $first = 1, $last = 4

Dict destructuring (explicit key mapping):

[name: "test", count: 42] -> *<name: $n, count: $c>
# $n = "test", $c = 42

Nested destructuring:

[[1, 2], 3] -> *<*<$a, $b>, $c>
# $a = 1, $b = 2, $c = 3

Errors:

[1, 2] -> *<$a, $b, $c>           # Error: pattern has 3 elements, list has 2
[name: "x"] -> *<name: $n, age: $a>  # Error: key 'age' not found

Slice /<>

Extract a portion using Python-style start:stop:step. Works on lists and strings.

Basic slicing:

[0, 1, 2, 3, 4] -> /<0:3>        # [0, 1, 2]
[0, 1, 2, 3, 4] -> /<1:4>        # [1, 2, 3]

Omitted bounds:

[0, 1, 2, 3, 4] -> /<:3>         # [0, 1, 2] (first 3)
[0, 1, 2, 3, 4] -> /<2:>         # [2, 3, 4] (from index 2)

Negative indices:

[0, 1, 2, 3, 4] -> /<-2:>        # [3, 4] (last 2)
[0, 1, 2, 3, 4] -> /<:-1>        # [0, 1, 2, 3] (all but last)

Step:

[0, 1, 2, 3, 4] -> /<::2>        # [0, 2, 4] (every 2nd)
[0, 1, 2, 3, 4] -> /<::-1>       # [4, 3, 2, 1, 0] (reversed)

String slicing:

"hello" -> /<1:4>                # "ell"
"hello" -> /<::-1>               # "olleh"

Edge cases:

[1, 2, 3] -> /<0:100>            # [1, 2, 3] (clamped)
[1, 2, 3] -> /<2:1>              # [] (empty when start >= stop)
[1, 2, 3] -> /<::0>              # Error: step cannot be zero

Member Access Operators

Field Access .field

Access dict fields:

[name: "alice", age: 30] => $person
$person.name                     # "alice"
$person.age                      # 30

Index Access [n]

Access list elements (0-based, negative from end):

["a", "b", "c"] => $list
$list[0]                         # "a"
$list[-1]                        # "c"
$list[1]                         # "b"

Variable Key .$key

Use a variable as key:

"name" => $key
[name: "alice"] => $data
$data.$key                       # "alice"

Computed Key .($expr)

Use an expression as key:

0 => $i
["a", "b", "c"] => $list
$list.($i + 1)                   # "b"

Alternative Keys .(a || b)

Try keys left-to-right:

[nickname: "Al"] => $user
$user.(name || nickname)         # "Al"

Hierarchical Dispatch

Navigate nested data structures using a list of keys/indexes as a path:

["name", "first"] -> [name: [first: "Alice", last: "Smith"]]
# Result: "Alice"

Path Syntax

Pipe a list path to a target structure. Path elements are applied sequentially:

  • Strings navigate dict fields
  • Numbers index into lists
  • Empty path returns target unchanged

Dict Path

["address", "city"] -> [address: [street: "Main", city: "Boston"]]
# Result: "Boston"

List Path

[0, 1] -> [[1, 2, 3], [4, 5, 6]]
# Result: 2 (first list, second element)

Mixed Path

["users", 0, "name"] -> [users: [[name: "Alice"], [name: "Bob"]]]
# Result: "Alice"

Empty Path

[] -> [name: "test"]
# Result: [name: "test"] (unchanged)

Error Handling

["missing"] -> [name: "test"]    # Error: key 'missing' not found
[5] -> [1, 2, 3]                 # Error: index 5 out of bounds

See Reference for full dispatch semantics including dict dispatch, list dispatch, and default values.


Default Operator ??

Provide a default value if field is missing or access fails:

[:] => $empty
$empty.name ?? "unknown"         # "unknown"

[name: "alice"] => $user
$user.name ?? "unknown"          # "alice"
$user.age ?? 0                   # 0

With Function Calls

The default operator works with any expression, including function and method calls:

get_data().status ?? "default"   # "default" if status field missing
fetch_value() ?? "fallback"      # "fallback" if fetch_value returns undefined

With Method Calls

The ?? operator applies after method invocations in access chains:

$dict.transform() ?? "default"   # default if method throws or result missing
$obj.compute().value ?? 0        # default if value field missing after method
$config.get_setting() ?? [:]     # default if method returns undefined

Method calls evaluate fully before the default operator applies.


Existence Operators

Field Existence .?field

Returns boolean:

[name: "alice"] => $user
$user.?name                      # true
$user.?age                       # false

Existence with Type .?field&type

Check existence AND type:

[name: "alice", age: 30] => $user
$user.?name&string               # true
$user.?age&number                # true
$user.?age&string                # false

Type Operators

Type Assert :type

Error if type doesn’t match, returns value unchanged:

42:number                        # 42
"hello" -> :string               # "hello"
"hello" -> :number               # ERROR: expected number, got string

Type Check :?type

Returns boolean:

42:?number                       # true
"hello":?number                  # false
"hello" -> :?string              # true

See Types for detailed type system documentation.


Operator Precedence

From highest to lowest:

  1. Member access: .field, [index]
  2. Type operators: :type, :?type
  3. Unary: -, !
  4. Multiplicative: *, /, %
  5. Additive: +, -
  6. Comparison: ==, !=, <, >, <=, >=
  7. Logical AND: &&
  8. Logical OR: ||
  9. Default: ??
  10. Pipe: ->
  11. Capture: =>

Use parentheses to override precedence:

(2 + 3) * 4                      # 20
5 -> ($ > 3) ? "big" ! "small"   # "big"

See Also