Skip to main content

Expressions

Quick Reference​

ExpressionSyntaxExample
Arithmetic+, -, *, /x + 1
Comparison==, !=, <, >, <=, >=x > 0
Logicaland, or, nota and b
String interpolation${}"Hello ${name}"
Field access.fielduser.name
Projection[field1, field2]data[id, name]
Merge+a + b
Conditionalif/elseif (x > 0) "pos" else "neg"
Branchbranch { ... }branch { cond -> val, otherwise -> default }
Matchmatch { ... }match x { { a } -> a, _ -> 0 }
Guardexpr when condx when x > 0
Coalesce??optional ?? default
Lambda(param) => expr(x) => x > 0
Implicit itit in HOF argsfilter(numbers, it > 0)
Infix HOFcoll verb exprnumbers filter it > 0
Record literal{ field: value }{ name: "Alice", age: 30 }
List literal[value, ...][1, 2, 3]

Variable References​

Reference a previously declared variable:

in x: Int
y = x # Reference x
out y

Function Calls​

Call registered functions (services, transformations):

result = function-name(arg1, arg2, ...)

Function names can include hyphens:

user = fetch-user-profile(userId)
orders = get-recent-orders(customerId, limit)

Arithmetic Expressions​

Perform arithmetic operations on numeric values:

in a: Int
in b: Int

sum = a + b # Addition
diff = a - b # Subtraction
product = a * b # Multiplication
quotient = a / b # Division

Arithmetic operators work with Int and Float types:

in x: Float
in y: Float

result = x * y + 1.5

Operator Precedence:

  • * and / have higher precedence than + and -
  • Use parentheses to control evaluation order: (a + b) * c
tip

When in doubt about precedence, use parentheses! They make your intent clear and prevent subtle bugs. For example, a + b * c means a + (b * c), not (a + b) * c.

Comparison Expressions​

Compare values and produce Boolean results:

in a: Int
in b: Int

isEqual = a == b # Equality
isNotEqual = a != b # Inequality
isGreater = a > b # Greater than
isLess = a < b # Less than
isGte = a >= b # Greater than or equal
isLte = a <= b # Less than or equal

Comparisons work with numeric types and return Boolean:

in score: Int
in threshold: Int

passed = score >= threshold
result = if (passed) score else 0

Field Access Expressions​

Access individual fields from a record using dot notation:

in user: { id: Int, name: String, email: String }

userId = user.id # Type: Int
userName = user.name # Type: String

Field access works on List<Record> element-wise:

in items: List<{ id: String, score: Float }>

ids = items.id # Type: List<String>
scores = items.score # Type: List<Float>

Note: Field access (.field) extracts a single field's value, while projection ([field1, field2]) creates a new record with selected fields.

Merge Expressions​

Combine record types using +:

in a: { x: Int }
in b: { y: String }
merged = a + b # Type: { x: Int, y: String }

See Type Algebra for details.

Projection Expressions​

Select specific fields from a record:

in data: { id: Int, name: String, email: String, extra: String }
result = data[id, name, email] # Type: { id: Int, name: String, email: String }

Projections work on List<Record> element-wise:

in items: List<{ id: String, score: Float, metadata: String }>
result = items[id, score] # Type: List<{ id: String, score: Float }>

Comparison Expressions​

Compare values using comparison operators:

in x: Int
in y: Int
isEqual = x == y # Equality
isNotEqual = x != y # Inequality
isLess = x < y # Less than
isGreater = x > y # Greater than
isLessEq = x <= y # Less than or equal
isGreaterEq = x >= y # Greater than or equal
out isEqual

All comparison operators return Boolean.

Supported comparisons:

OperatorTypesDescription
==Int, StringEquality
!=Int, StringInequality
<IntLess than
>IntGreater than
<=IntLess than or equal
>=IntGreater than or equal

Boolean Expressions​

Combine boolean values using logical operators:

in isActive: Boolean
in hasPermission: Boolean
in score: Float

# Logical AND - both must be true
canAccess = isActive and hasPermission

# Logical OR - at least one must be true
isEligible = hasPermission or score >= 0.9

# Logical NOT - negates the value
isBlocked = not isActive

out canAccess

Operator precedence (lowest to highest):

  1. or - Logical OR
  2. and - Logical AND
  3. not - Logical NOT
  4. Comparison operators (==, !=, <, >, <=, >=)
  5. Merge operator (+)

Short-circuit evaluation:

Boolean operators use short-circuit evaluation:

  • a and b: If a is false, b is not evaluated
  • a or b: If a is true, b is not evaluated

This is important when b involves expensive operations:

# If hasCache is true, expensiveComputation is never called
result = hasCache or expensiveComputation(data)

Complex expressions:

Use parentheses to control evaluation order:

# Without parentheses: a or (b and c)
result1 = a or b and c

# With parentheses: (a or b) and c
result2 = (a or b) and c

Conditional Expressions​

result = if (condition) thenExpr else elseExpr

Both branches must have the same type:

in flag: Boolean
in a: Int
in b: Int
result = if (flag) a else b # Type: Int

Guard Expressions (when)​

warning

Guard expressions return Optional types. Use the coalesce operator (??) to unwrap the result with a fallback value. See Guard Expressions for details.

Attach a boolean guard to any expression for conditional execution:

result = expression when condition

The result has type Optional<T> where T is the type of expression. If condition is false, the result is None:

in data: String
in minLength: Int

# Only process sufficiently long data
processed = transform-data(data) when length(data) > minLength

# Conditional feature extraction
in user: { tier: String }
premium_features = get-premium-data(user) when user.tier == "premium"

Guards compose with boolean operators:

result = expensive-op(data) when (flag and not disabled) or override

Coalesce Operator (??)​

Provide fallback values for optional expressions:

result = optional_expr ?? fallback_expr

If optional_expr is Some(v), the result is v. Otherwise, the result is fallback_expr:

# Fallback to computed value if cache miss
result = get-cached(id) ?? compute-fresh(data)

# Chain of fallbacks
value = primary() ?? secondary() ?? default_value

Branch Expressions​

tip

Use branch instead of nested if/else when you have more than two conditions. The otherwise clause is required for exhaustiveness, ensuring you handle all cases.

Multi-way conditional with exhaustive matching:

result = branch {
condition1 -> expression1,
condition2 -> expression2,
otherwise -> default_expression
}

Conditions are evaluated in order; the first matching branch is returned. The otherwise clause is required for exhaustiveness:

# Tiered processing based on priority
processed = branch {
priority == "high" -> fast-path(data),
priority == "medium" -> standard-path(data),
otherwise -> batch-path(data)
}

# Service selection based on input size
output = branch {
length(data) > 1000 -> heavy-processor(data),
length(data) > 100 -> standard-processor(data),
otherwise -> light-processor(data)
}

Match Expressions​

tip

Use match to discriminate between union type variants. The compiler checks exhaustiveness at compile time, ensuring you handle all possible cases.

Pattern match on union types with exhaustive case handling:

result = match scrutinee {
{ field1, field2 } -> expression1,
{ otherField } -> expression2,
_ -> default_expression
}

Each pattern binds its fields as variables in the body expression:

type Result = { value: Int, status: String } | { error: String, code: Int }
in response: Result

message = match response {
{ value, status } -> "Got ${value} with status ${status}",
{ error, code } -> "Error ${code}: ${error}"
}
out message

Pattern Types​

PatternMatchesBinds
{ field1, field2 }Records with these fieldsEach field as a variable
_Any value (wildcard)Nothing

Exhaustiveness​

The compiler verifies all union variants are covered:

type State = { active: Boolean } | { pending: Int } | { banned: String }
in state: State

# This will compile error - missing { pending } and { banned }
# result = match state { { active } -> active }

# This is valid - wildcard covers remaining variants
result = match state {
{ active } -> active,
_ -> false
}

Lambda Expressions​

Define inline functions for use with higher-order functions like filter, map, all, and any:

(parameter) => expression
(param1, param2) => expression

Lambdas enable collection operations:

in items: List<{ score: Float, active: Boolean }>

# Infix HOF with implicit it
highScoring = items filter it.score > 0.8

# Prefix with implicit it
doubled = map(items, it.score * 2)

# Explicit lambda
allActive = all(items, (item) => item.active)
anyHighScore = any(items, (item) => item.score > 0.9)

Infix HOF Syntax​

filter, map, all, any work as infix operators with left-to-right chaining:

numbers filter it > 0 map it * 2
# equivalent to: map(filter(numbers, it > 0), it * 2)

Implicit it Parameter​

When a HOF expects a single-parameter lambda, use it as an implicit parameter:

filter(numbers, it > 0)    # desugars to: filter(numbers, (it) => it > 0)

See Lambdas for full details on all three calling forms.

String Interpolation​

note

String interpolation uses ${} syntax (not #{} or %s). The expression inside the braces is evaluated and converted to a string automatically.

Embed expressions within string literals using ${}:

in name: String
in count: Int

greeting = "Hello, ${name}!"
summary = "Processed ${count} items"

Expressions inside ${} are evaluated and converted to strings:

in user: { firstName: String, lastName: String }
in score: Float

fullName = "${user.firstName} ${user.lastName}"
result = "Score: ${score * 100}%"

String interpolation works with any expression that produces a string-convertible value:

in items: List<String>

message = "Found ${length(items)} items"
status = "Ready: ${isReady and hasPermission}"

Literals​

Primitive Literals​

stringVal = "hello world"
intVal = 42
floatVal = 3.14
boolVal = true

List Literals​

numbers = [1, 2, 3]
names = ["Alice", "Bob"]
empty = []

Record Literals​

Inline record construction using brace syntax:

user = { name: "Alice", age: 30 }
config = { timeout: 5000, retries: 3 }
nested = { outer: { inner: "value" } }

Record literals are commonly used with @example annotations for union type inputs:

type ApiResult = { value: Int, status: String } | { error: String, code: Int }

@example({ value: 42, status: "ok" })
in response: ApiResult

Parentheses​

Group expressions for clarity:

result = (a + b)[field1, field2]
  • Types — Type system and type compatibility
  • Guards — Conditional execution with when
  • Coalesce — Fallback values with ??
  • Lambdas — Inline functions for collections