Language specification
This page defines the language. It stops at the interpreter boundary: datastores, HTTP and message buses are registered by the embedding host as builtins under its own namespaces, and examples that call one are marked as host-provided.
Function declaration01
fn <name>(<params>) -> <return_type>:
<body>
-- or, for a single expression --
fn <name>(<params>) -> <return_type> => <expression>Parameters take three forms:
name: type -- required
name: type = default_value -- optional, with a default
...name: type -- variadic, and must be lastTypes
string, int, float, bool, datetime, duration, object, any and void,
plus the array variant of each: string[], float[], object[] and so on.
Both forms
fn celsius_to_fahrenheit(temp: float) -> float =>
temp * 1.8 + 32fn classify_temperature(temp: float, unit: string = "celsius") -> string:
let normalized = if unit == "fahrenheit" then (temp - 32) / 1.8 else temp
match normalized:
when < 0 => "freezing"
when < 15 => "cold"
when >= 35 => "hot"Variables02
let x = 42
let name = "sensor_01"
let values = [1, 2, 3, 4, 5]Once bound, a name cannot be reassigned. There is no assignment operator to reach for. This is what makes a function free of side effects and safe to evaluate in parallel.
Conditionals03
An if is an expression, inline or laddered.
let status = if temp > 80 then "hot" else "ok"
let category =
if score > 90 then "excellent"
else if score > 70 then "good"
else if score > 50 then "average"
else "poor"Pattern matching04
A match arm tests a comparison, a literal, a range, or the wildcard _.
match value:
when < 0 => "negative"
when 0 => "zero"
when 1..10 => "small"
when 11..100 => "medium"
when > 100 => "large"
match status_code:
when "OK" => 0
when "WARN" => 1
when "ERROR" => 2
when _ => -1The pipe operator05
| chains transformations left to right. It is the core ergonomic feature, and the
reason a function stays readable once it does more than one thing.
-- instead of: round(avg(filter(values, x => x > 0)), 2)
values | filter(> 0) | avg() | round(2)Pipes also chain over whatever the host exposes. If an embedder registers a query
builtin returning a row list, it composes the same way:
query("sensor_readings")
| where timestamp > $start_date
| select temperature, humidity
| order_by timestamp desc
| limit 100Collection operations06
values | map(x => x * 2)
values | filter(x => x > threshold)
values | filter(> 0) -- implicit argument
values | reduce(0, (acc, x) => acc + x)
values | sort() -- sort(desc) for descending
values | head(5) -- tail(10) for the other end
values | unique()
values | flatten()
values | zip(other_values)
values | group_by(x => x.category)
values | chunk(5)Aggregation07
values | sum() values | avg()
values | min() values | max()
values | count() values | stdev()
values | variance() values | median()
values | percentile(95)
values | count_where(> 100)
values | sum_where(> 0)Strings08
name | upper() name | lower()
name | trim() name | replace("old", "new")
name | split(",") parts | join(", ")
name | starts_with("pre")
name | contains("sub")
name | substr(0, 5)
"Hello {name}, temp is {temp | round(1)}"Dates and times09
now() today()
dt | add(7, "days") dt | subtract(1, "hours")
dt | diff(other, "minutes")
dt | format("YYYY-MM-DD")
dt | year() dt | month()
dt | day() dt | hour()
dt | day_of_week() -- 0 is Sunday
dt | start_of("month") dt | end_of("week")Maths10
abs(x) round(x, decimals)
ceil(x) floor(x)
power(x, n) sqrt(x)
log(x) log10(x)
clamp(x, min, max)
lerp(a, b, t)Error handling11
let result = try some_function(x) catch default_value
let safe_value = value ?? default_value -- null coalescing
let safe_result = value?.nested?.field -- optional chainingComments12
-- single line
{-
multi-line
-}Type casting13
value | as_float() value | as_int()
value | as_string() value | as_bool()
value | as_datetime("YYYY-MM-DD")Namespaces14
Functions live in namespaces, which serve both organisation and access control. A fully-qualified name stays valid once a function is shared.
system::math::clamp -- built in
shared::analytics::anomaly -- shared across all users
team::data_eng::normalize -- team-scoped
user::jane::my_helper -- private to a user
app::iot_connector::decode -- provided by an appNext: the standard library, which is written in DTL itself.