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