Language
Types & data
Core values: text, number, bool, list, map, and nothing. Beyond them: decimal (exact base-10, for money), bytes, complex, numeric array, and the time types date, datetime and duration — see Stdlib. A lambda or task is a value too.
-- Doc example: types, property access, custom types, value semantics, map/list helpers,
-- conversions and the intentional operations.
intent: "doc example: types and data"
type Point
x: number
y: number
let demo_p be Point(3, 4)
print("Point(3,4) → x=" + text(x of demo_p) + " y=" + text(y of demo_p))
-- The message of the error `f` raises, or nothing when it does not raise.
task error_of(f)
try
f()
recover err
give err
give nothing
task add_tag(item)
set item["tag"] to "new"
give item
test "type_of names every kind of value"
assert_eq(type_of("a"), "text")
assert_eq(type_of(1), "number")
assert_eq(type_of(1.5), "number")
assert_eq(type_of(true), "bool")
assert_eq(type_of([1]), "list")
assert_eq(type_of({"a": 1}), "map")
assert_eq(type_of(nothing), "nothing")
assert_eq(type_of(1.5d), "decimal")
assert_eq(type_of(bytes([1])), "bytes")
assert_eq(type_of(array([1, 2])), "array")
assert_eq(type_of(complex(1, 2)), "complex")
assert_eq(type_of(date(2026, 1, 3)), "date")
assert_eq(type_of(datetime("2026-01-03T10:00:00Z")), "datetime")
assert_eq(type_of(duration(hours = 1)), "duration")
assert_eq(type_of((x) => x), "task")
assert(is_integer(1) and is_text("a") and is_list([]) and is_map({}))
assert_eq(is_integer(1.0), false)
test "integers are exact at any size; decimals are exact; floats are floats"
assert_eq(2 ** 64, 18446744073709551616)
assert_eq(1.1d + 2.2d, 3.3d)
assert_eq(0.1 + 0.2 == 0.3, false)
assert_error(() => 1d == 1.0)
test "conversions: text, int, number, hex"
assert_eq(text(42), "42")
assert_eq(int("42"), 42)
assert_eq(int("0x1f"), 31)
assert_eq(int("ff", base = 16), 255)
assert_eq(int("abc", fallback = 0), 0)
assert_eq(number("42"), 42.0)
assert_eq(hex(255), "0xff")
assert_error(() => int(1.5))
test "values + property access (of / dot / index)"
let cfg be {"host": "localhost", "port": 8080}
assert_eq(port of cfg, 8080)
assert_eq(cfg.host, "localhost")
assert_eq(cfg["port"], 8080)
assert_eq(length([1, 2, 3]), 3)
assert_eq([1, 2, 3][-1], 3)
assert(contains(error_of(() => cfg["user"]), "Map has no key 'user'"))
assert_eq(get(cfg, "user", "anon"), "anon")
test "custom type + positional constructor"
let p be Point(3, 4)
assert_eq(x of p, 3)
assert_eq(y of p, 4)
assert_eq(p, {"x": 3, "y": 4})
assert(contains(error_of(() => Point(3)), "expects 2 fields, got 1"))
test "lists and maps are values: copies are independent"
let a be {"n": 1}
let b be a
set b["n"] to 2
assert_eq(a["n"], 1)
let item be {"id": 7}
let tagged be add_tag(item)
assert_eq(item, {"id": 7})
assert_eq(tagged, {"id": 7, "tag": "new"})
let p be Point(3, 4)
let q be p
set q.y to 99
assert_eq(p.y, 4)
test "map helpers: get, items, keys, values, merge, remove, in"
let m be {"a": 1, "b": 2}
assert_eq(get(m, "a"), 1)
assert_eq(get(m, "z"), nothing)
assert_eq(get(m, "z", 0), 0)
assert_eq(items(m), [{"key": "a", "value": 1}, {"key": "b", "value": 2}])
assert_eq(keys(m), ["a", "b"])
assert_eq(values(m), [1, 2])
assert_eq(merge(m, {"b": 3}, {"c": 4}), {"a": 1, "b": 3, "c": 4})
assert_eq(remove(m, "a"), {"b": 2})
assert_eq(m, {"a": 1, "b": 2})
assert("a" in m)
set m["c"] to 3
assert_eq(length(m), 3)
test "list helpers: append, insert, get, slice, index_of, reverse"
let xs be [1, 2, 3]
assert_eq(append(xs, 4), [1, 2, 3, 4])
assert_eq(insert(xs, 0, 0), [0, 1, 2, 3])
assert_eq(insert(xs, -1, 9), [1, 2, 9, 3])
assert_eq(get(xs, 5, "none"), "none")
assert_eq(slice(xs, 1), [2, 3])
assert_eq(index_of(xs, 3), 2)
assert_eq(index_of(xs, 7), nothing)
assert_eq(reverse(xs), [3, 2, 1])
assert_eq(xs + [4], [1, 2, 3, 4])
assert_eq(xs, [1, 2, 3])
test "intentional ops replace loops"
let nums be [1, 2, 3, 4]
assert_eq(apply((n) => n * 2, nums), [2, 4, 6, 8])
assert_eq(where(nums, (n) => n > 2), [3, 4])
assert_eq(reduce(nums, (a, b) => a + b, 0), 10)
assert_eq(sort([3, 1, 2]), [1, 2, 3])
assert_eq(sort([3, 1, 2], desc = true), [3, 2, 1])
assert_eq(sort([2, nothing, 1], desc = true), [2, 1, nothing])
assert_eq(sort_by([3, 1, 2], (v) => v), [1, 2, 3])
assert_eq(sort_by([3, 1, 2], (v) => v, desc = true), [3, 2, 1])
assert_eq(collect([{"a": 1}, {"a": 2}], "a"), [1, 2])
assert_eq(count_where(nums, (n) => n % 2 == 0), 2)
assert_eq(apply(enumerate(["a", "b"]), (e) => e.index), [0, 1])What a value is§
type_of(x) names it: "text", "number", "bool", "list", "map", "nothing", "decimal", "bytes", "complex", "array", "date", "datetime", "duration", "task". The checks is_integer(x), is_text(x), is_list(x) and is_map(x) answer one question each (is_integer(1.0) is false: it is a float with a whole value).
- A number is an integer or a float. Integers are exact at any size (
2 ** 64is
18446744073709551616); / always gives a float, // keeps integers exact. 0.1 + 0.2 is a float and not 0.3; for money use decimals (1.1d + 2.2d == 3.3d). A decimal compared with a float is an error — convert one on purpose.
- Conversions:
text(x);int(x)→ an exact integer from text ("42","0x1f") or a whole
float, with the base and the fallback by name — int("ff", base = 16), int("abc", fallback = 0); int(1.5) is an error (use floor, round or trunc); number(x) → always a float; hex(n) → "0xff".
Property access — three ways§
host of config -- natural
config.host -- dot
config["host"] -- index
A missing key is an error (Map has no key 'user'); get(config, "user", "anon") returns a default instead. Lists, text and bytes take negative indexes (xs[-1] is the last item).
Custom types§
A type declares named fields; the constructor is positional in field order and takes exactly one argument per field (Type Point expects 2 fields, got 1). The result is a map with those fields:
type Customer
name: text
email: text
balance: number
let c be Customer("Ada", "ada@x.com", 500)
let who be name of c
Lists and maps are values§
Binding a list or map, or passing it to a task, gives a logical copy (copy-on-write: nothing is copied until someone writes, so it costs nothing to pass one around):
let a be {"n": 1}
let b be a
set b["n"] to 2 -- a["n"] is still 1
A task that sets inside a map it received changes its own copy: to hand the change back, give it and set it in the caller (set item to add_tag(item)). The same holds for each r in rows — set r["x"] to 1 changes the loop's copy; write by index (set rows[i]["x"] to 1) or rebuild with apply + merge. Shared state is always explicit: the blackboard, memory, the bus, state_*, or an exported module variable (Modules). set xs to append(xs, x), set m[k] to v and set xs to insert(xs, i, v) update in place when nobody else holds the value, so building a list in a loop stays fast.
Map and list helpers§
Every helper returns a NEW value and leaves its argument alone:
get(m, k)→ the value ornothing;get(m, k, default); on listsget(xs, i, default)
(negative i counts from the end).
items(m)→[{key, value}, …]in insertion order;keys(m),values(m);k in m.merge(a, b, …)→ one map, the rightmost value wins;remove(m, k)→ the map withoutk.append(xs, v),xs + ys,insert(xs, i, v)(ifrom0tolength, negatives like Python,
out of range is an error), slice(xs, start, end?), index_of(xs, v) (nothing when absent), reverse(xs), enumerate(xs) → [{index, item}, …] (engine v0.6.46+: also an array, by its first axis).
Intentional operations (instead of loops)§
Express intent and let the runtime iterate:
apply(fn, list)— map each item.where(list, predicate)— filter.reduce(list, fn, initial)— fold to one value.sort(list)/sort(list, desc = true),sort_by(list, key_fn)/sort_by(list, key_fn, desc = true)— stable;nothinggoes last.collect(list, "field"),count_where(list, predicate).
This reads better than a loop and is what an LLM should reach for first. Tables (lists of maps) have their own operations — summarize, group_by, join, pivot — in CSV, stats & charts.