Expressions
Quick Reference
| Expression | Syntax | Example |
|---|---|---|
| Arithmetic | +, -, *, / | x + 1 |
| Comparison | ==, !=, <, >, <=, >= | x > 0 |
| Logical | and, or, not | a and b |
| String interpolation | ${} | "Hello ${name}" |
| Field access | .field | user.name |
| Projection | [field1, field2] | data[id, name] |
| Merge | + | a + b |
| Conditional | if/else | if (x > 0) "pos" else "neg" |
| Branch | branch { ... } | branch { cond -> val, otherwise -> default } |
| Match | match { ... } | match x { { a } -> a, _ -> 0 } |
| Guard | expr when cond | x when x > 0 |
| Coalesce | ?? | optional ?? default |
| Lambda | (param) => expr | (x) => x > 0 |
Implicit it | it in HOF args | filter(numbers, it > 0) |
| Infix HOF | coll verb expr | numbers 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
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:
| Operator | Types | Description |
|---|---|---|
== | Int, String | Equality |
!= | Int, String | Inequality |
< | Int | Less than |
> | Int | Greater than |
<= | Int | Less than or equal |
>= | Int | Greater 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):
or- Logical ORand- Logical ANDnot- Logical NOT- Comparison operators (
==,!=,<,>,<=,>=) - Merge operator (
+)
Short-circuit evaluation:
Boolean operators use short-circuit evaluation:
a and b: Ifais false,bis not evaluateda or b: Ifais true,bis 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)
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
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
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
| Pattern | Matches | Binds |
|---|---|---|
{ field1, field2 } | Records with these fields | Each 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
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]