Collections & Lambdas
Range operator ..
start..end produces an inclusive list[int] containing every integer from start to end.
digits = 1..5 # [1, 2, 3, 4, 5]
single = 4..4 # [4]
empty = 5..3 # [] (start > end → empty)
Both operands must be int — using a float or str is a type error.
Ranges work directly with for:
for i in 0..2 {
io.notify("{i}") # 0, 1, 2
}
Because the result is a plain list[int], all list methods apply:
count = (1..10).count() # 10
evens = (1..10).filter(n => n % 2 == 0) # [2, 4, 6, 8, 10]
Lists
nums = [1, 2, 3, 4, 5]
names = ["alice", "bob", "charlie"]
empty = []
List concatenation and adding elements
Concatenate lists with + or add a single element with push:
base = ["a", "b"]
extra = ["c", "d"]
all = base + extra # ["a", "b", "c", "d"]
more = all.push("e") # ["a", "b", "c", "d", "e"]
push returns a new list — it does not mutate in place. Reassign if needed: items = items.push(x).
Collection methods
All methods accept lambdas (x => expr) or task references.
map — transform each element
doubled = [1, 2, 3].map(n => n * 2) # [2, 4, 6]
names = contacts.map(c => c.name) # extract names
filter — keep matching elements
big = [1, 5, 10, 20].filter(n => n > 5) # [10, 20]
urgent = emails.filter(e => e.urgency == high)
find — first matching element
found = items.find(n => n > 15) # first match or none
admin = users.find(u => u.role == "admin") ?? default_user
any / all — boolean checks
has_urgent = emails.any(e => e.urgency == critical) # true/false
all_done = tasks.all(t => t.status == "complete")
reduce — fold to a single value
The first argument is the combining function (accumulator, element) => ...; the second is the initial accumulator value.
total = [1, 2, 3, 4, 5].reduce((acc, x) => acc + x, 0) # 15
joined = words.reduce((acc, w) => acc + " " + w, "")
sum / min / max — numeric aggregation
prices = [9.99, 4.50, 14.00]
prices.sum() # 28.49
prices.min() # 4.50
prices.max() # 14.00
min() and max() return none on an empty list. sum() requires a numeric list; calling it on non-numeric elements is a runtime error.
join — concatenate to string
["a", "b", "c"].join(", ") # "a, b, c"
tags.join(" | ")
sort — natural ordering
Returns a new sorted list. Integers, floats, and strings are compared by value. Structs with impl Comparable are sorted by their compare method.
[3, 1, 4, 1, 5].sort() # [1, 1, 3, 4, 5]
["cherry", "apple", "banana"].sort() # ["apple", "banana", "cherry"]
sort(by:) — sort by key function
Pass an optional by: key function to .sort() — consistent with the min(by:) / max(by:) pattern. The key must return an int, float, or str. Ascending only; negate numeric keys for descending.
type Product { name: str, price: float, rating: float }
# Sort by a single field
by_price = products.sort(by: p => p.price) # cheapest first
by_name = products.sort(by: p => p.name) # alphabetical
# Descending — negate the key
by_rating = products.sort(by: p => 0.0 - p.rating) # highest rated first
reverse — flip order
[1, 2, 3].reverse() # [3, 2, 1]
top3 = scores.sort().reverse().take(3)
flatten — unwrap one level of nesting
[[1, 2], [3], [4, 5]].flatten() # [1, 2, 3, 4, 5]
zip — pair two lists
Returns a new list where each element is a 2-element tuple [a, b] drawn from the two input
lists in order. Stops when the shorter list is exhausted.
names = ["alice", "bob", "carol"]
scores = [90, 85, 95]
pairs = names.zip(scores)
# → [["alice", 90], ["bob", 85], ["carol", 95]]
for (name, score) in names.zip(scores) {
log.info("{name} scored {score}")
}
The return type is inferred as list[(T, U)], so the loop variables name and score are
fully typed.
take / skip — slice by count
[10, 20, 30, 40, 50].take(3) # [10, 20, 30]
[10, 20, 30, 40, 50].skip(3) # [40, 50]
Lambda syntax
# Single parameter
n => n * 2
# Multi-parameter
(a, b) => a + b
# Block body
items.map(item => {
urgency = triage(item)
{item: item, urgency: urgency}
})
Task references
Named tasks can be passed directly to collection methods:
task double(n: int) -> int { n * 2 }
task is_even(n: int) -> bool { n % 2 == 0 }
result = [1, 2, 3].map(double) # [2, 4, 6]
evens = [1, 2, 3, 4].filter(is_even) # [2, 4]
Map / struct access
info = {name: "Alice", age: 30}
info.name # "Alice"
info.age # 30
# Nested
team = {lead: {name: "Bob", role: "eng"}}
team.lead.name # "Bob"
Maps
A typed map[K, V] literal is built with the same {k: v, ...} form.
The same syntax serves both struct construction and map[str, V]
construction; the checker resolves which based on the declared type.
stock: map[str, int] = {apples: 12, pears: 5, plums: 0}
Map methods
| Method | Returns |
|---|---|
map.get(k) | V? — none if the key is absent |
map.keys() | list[K] |
map.values() | list[V] |
map.len() / map.count() / map.size() | int |
map.is_empty() | bool |
map.contains(k) / map.has(k) | bool |
stock: map[str, int] = {apples: 12, pears: 5}
stock.count() # 2
stock.contains("apples") # true
stock.keys() # ["apples", "pears"]
stock.values() # [12, 5]
# get returns V? — coalesce or assert before using as V.
pears = stock.get("pears") ?? 0 # 5
missing = stock.get("oranges") ?? 0 # 0
String methods
s = " Hello, World! "
s.trim() # "Hello, World!"
s.upper() # " HELLO, WORLD! "
s.lower() # " hello, world! "
s.contains("Hello") # true
s.starts_with(" H") # true
s.split(", ") # [" Hello", "World! "]
s.replace("World", "Keel") # " Hello, Keel! "
"42".to_int() # 42 (int?)