Example Gallery
Language features, algorithms, data structures, and real programs — all with explanations
Language Features
Six constructs that define Synoema's design. Each is chosen for expressiveness and LLM token efficiency.
Pattern Matching
Functions are defined by multiple equations — each with its own pattern. The first matching equation wins. No case, no match, no indentation rules. The compiler warns if patterns are non-exhaustive.
fac 0 = 1
fac n = n * fac (n - 1)
head (x:_) = x
tail (_:xs) = xs
classify 0 = "zero"
classify n = ? n > 0 -> "positive" : "negative"
Pipe Operator |>
The |> pipe feeds the result of one function into the next. Data flows left-to-right, stage by stage. Each step is a separate transformation — readable by humans and predictable for LLMs generating code incrementally.
result = [1..20]
|> filter even
|> map (\x -> x * x)
|> foldl (\a b -> a + b) 0
-- 1540
words = ["alice" "bob" "carol"]
|> map (\s -> "Hello, ${s}!")
|> map str_len
Algebraic Data Types
Types are defined as a union of constructors. Pattern matching on constructors is total — the language catches missing cases at compile time. No null, no casting, no isinstance.
Shape = Circle Float | Rect Float Float | Point
area (Circle r) = 3.14159 * r * r
area (Rect w h) = w * h
area Point = 0.0
describe (Circle r) = "circle r=${show r}"
describe (Rect w h) = "rect ${show w}x${show h}"
describe Point = "point"
Result Type & Error Chains
Functions that can fail return Ok value or Err msg. The and_then combinator chains operations — if any step fails, the error propagates automatically. No try/catch, no null checks in the happy path.
safe_div _ 0 = Err "division by zero"
safe_div a b = Ok (a / b)
-- Chain without nested ifs
compute x =
a <- safe_div x 2
b <- safe_div a 3
Ok (a + b)
main = print (unwrap_or (-1) (compute 12))
Records & Spread Syntax
Records are structural types — create with {}, access with .field. The spread operator ... copies all fields and overrides the ones you name. Immutable by default; updates produce a new record.
user = {name = "Alice", age = 30, role = "user"}
user.name -- "Alice"
user.age -- 30
-- Non-destructive update
promoted = {...user, role = "admin"}
older = {...user, age = user.age + 1}
greet {name, role} = "${name} is ${role}"
Type Inference
Hindley-Milner inference: no type annotations needed, anywhere. Types are inferred globally — including polymorphic functions, ADT patterns, and higher-order functions. Zero annotation overhead for LLMs generating Synoema code.
-- Inferred: Int -> Int
double x = x * 2
-- Inferred: (a -> b) -> a -> b
apply f x = f x
-- Inferred: (a -> b) -> List a -> List b
map f [] = []
map f (x:xs) = f x : map f xs
-- Works with records and ADTs without annotations
Core Algorithms
Quicksort
Quicksort in two recursive equations. The list comprehension [y | y <- xs, y < x] filters elements smaller than the pivot — no temporary variables, no index arithmetic. Compare this to 20+ lines in an imperative language.
qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y < x]
++ [x]
++ qsort [y | y <- xs, y >= x]
main = print (qsort [3 1 4 1 5 9 2 6])
-- [1 1 2 3 4 5 6 9]
Factorial
The canonical pattern-matching example. The base case fac 0 = 1 matches exactly when n is zero; the recursive case handles everything else. Tail-call optimized in the compiler — no stack overflow for large inputs.
fac 0 = 1
fac n = n * fac (n - 1)
main = print (fac 10)
-- 3628800
Fibonacci with Memoization
Memoized Fibonacci using an explicit cache tuple threaded through the computation. Synoema's tuple destructuring keeps the accumulator pattern readable without mutation or global state.
fib_go 0 cache = (0, cache)
fib_go 1 cache = (1, cache)
fib_go n cache =
(a, c1) = fib_go (n - 1) cache
(b, c2) = fib_go (n - 2) c1
(a + b, c2)
fib n = fst (fib_go n [])
Binary Search with Contracts
Type annotation plus a requires precondition: the contract is enforced at runtime and extracted into docs by synoema doc --contracts. The function returns Ok index or Err "not found" — never throws.
bsearch : List Int -> Int -> Result Int String
requires length xs > 0
--- Binary search on sorted list.
--- example: bsearch [1 2 3 4 5] 3 == Ok 2
bsearch xs target = go xs target 0 (length xs - 1)
go xs t lo hi =
? lo > hi -> Err "not found"
: mid = (lo + hi) / 2
val = index mid xs
? val == t -> Ok mid
: ? val < t -> go xs t (mid + 1) hi
: go xs t lo (mid - 1)
Data Pipeline
Multi-stage data transformation: each |> step is a named operation. The pipeline reads like a Unix shell command chain — ideal for LLMs that generate one transformation at a time and for humans reading the logic top-to-bottom.
cap_at limit xs =
map (\x -> ? x > limit -> limit : x) xs
main =
raw = [15 0 42 8 0 27 100 5]
raw
|> cap_at 50
|> filter (\x -> x > 0)
|> map (\x -> x * 2)
|> sum
|> print
-- 190
Error Handling Chain
Railway-oriented programming: and_then sequences operations that may fail. If any step returns Err, the chain short-circuits and the error propagates to the caller. The happy path reads linearly with no conditional nesting.
parse_int "0" = Ok 0
parse_int "42" = Ok 42
parse_int s = Err ("not a number: " ++ s)
safe_div a 0 = Err "division by zero"
safe_div a b = Ok (a / b)
parse_and_divide a_str b_str =
parse_int a_str
|> and_then (\a -> parse_int b_str
|> and_then (\b -> safe_div a b))
String Processing
Pure recursive string utilities — no mutable builders. String interpolation ${expr} works with any expression. str_slice, str_len, and str_find are builtins; everything else is defined in the prelude or user code.
str_reverse s =
? str_len s <= 1 -> s
: "${str_reverse (str_slice s 1 (str_len s))}\
${str_slice s 0 1}"
str_repeat s 0 = ""
str_repeat s n = "${s}${str_repeat s (n - 1)}"
str_contains haystack needle =
str_find haystack needle 0 >= 0
Pipeline: CLI Tool
Command dispatch via pattern matching on the argument list. Each pattern is a different command shape — adding a new command means adding one equation, no if-elif chain to extend.
dispatch [] = print "Usage: tool <cmd>"
dispatch ("greet" : name : _) = print "Hello, ${name}!"
dispatch ("version" : _) = print "v1.0"
dispatch ("help" : _) = dispatch []
dispatch _ = print "Unknown command"
main = dispatch args
Data Structures
Binary Search Tree
A typed BST built from an ADT with three operations: insert, member, inorder traversal. The Tree a type is polymorphic — works for any ordered type without annotations.
Tree a = Leaf | Node (Tree a) a (Tree a)
tree_insert x Leaf = Node Leaf x Leaf
tree_insert x (Node l v r) =
? x < v -> Node (tree_insert x l) v r
: ? x > v -> Node l v (tree_insert x r)
: Node l v r
tree_inorder Leaf = []
tree_inorder (Node l v r) =
tree_inorder l ++ [v] ++ tree_inorder r
main = print (tree_inorder
(foldl (\t x -> tree_insert x t) Leaf
[5 3 8 1 4 7 9]))
Queue via Two Stacks
An O(1) amortized queue implemented with two lists. The MkQueue constructor wraps the internal state — callers use queue_enqueue and queue_dequeue without knowing the representation.
Queue a = MkQueue (List a) (List a)
queue_new = MkQueue [] []
queue_enqueue x (MkQueue f b) = MkQueue f (x : b)
queue_dequeue (MkQueue [] []) = Err "empty"
queue_dequeue (MkQueue [] back) =
queue_dequeue (MkQueue (reverse back) [])
queue_dequeue (MkQueue (x:f) b) =
Ok (x, MkQueue f b)
State Machine
A traffic-light state machine where each state transition is a one-line equation. Adding a new state means adding one next equation — no switch, no lookup table.
Light = Red | Yellow | Green
next Red = Green
next Green = Yellow
next Yellow = Red
run_n state 0 = [show state]
run_n state n = show state : run_n (next state) (n - 1)
main = print (run_n Red 6)
-- ["Red" "Green" "Yellow" "Red" "Green" "Yellow" "Red"]
Records: User Model
Records with field punning, nested access, and spread updates. The {name, age} pattern in greet destructures the record directly into named variables — no intermediate getters needed.
make_user name age role = {name, age, role}
greet {name, role} = "${name} is ${role}"
promote user = {...user, role = "admin"}
birthday user = {...user, age = user.age + 1}
main =
u = make_user "Alice" 30 "user"
u2 = promote (birthday u)
print (greet u2)
-- Alice is admin
Geometry Module
The mod keyword creates a named namespace. use Vec2 (*) imports all public names into scope. Modules are the primary mechanism for organizing larger programs — no separate files needed for small domains.
mod Vec2
make x y = {x, y}
add a b = {x = a.x + b.x, y = a.y + b.y}
dot a b = a.x * b.x + a.y * b.y
len_sq v = v.x * v.x + v.y * v.y
use Vec2 (*)
p1 = make 3 4
p2 = make 1 2
main = print (len_sq (add p1 p2))
-- 52
Concurrency & Async
Stackless async/await with a mio epoll/kqueue reactor. async fn and await run on the JIT path — no callback pyramids, no thread-per-connection.
async fn / await
Async functions are stackless coroutines. await suspends the current task without blocking an OS thread — the reactor reschedules it when the result is ready. Works in both interpreter and JIT modes.
async fn delayed name =
_ = await (async_sleep 50)
"Hello, " ++ name
async fn main =
a = await (delayed "Alice")
b = await (delayed "Bob")
print (a ++ " & " ++ b)
-- Hello, Alice & Hello, Bob
race / gather
race runs tasks in parallel and returns the first result — the rest are dropped. gather runs all tasks in parallel and collects every result in order. Both are built-in; no external executor needed.
async fn fast = 1
async fn slow = _ = await (async_sleep 500); 2
async fn main =
winner = await (race [slow fast])
print winner -- 1 (fast wins)
all = await (gather [fast fast fast])
print (show all) -- [1, 1, 1]
scope_result / try_await
scope_result runs an async block and captures any error call as Err — the program continues instead of crashing. try_await does the same for a single task. Essential for LLM-generated code that may produce runtime errors.
async fn risky n =
? n == 0 -> error "zero input"
: n * 2
async fn main =
r = scope_result (\() -> await (risky 0))
? is_ok r -> print "ok"
: print ("caught: " ++ show (unwrap_err r))
-- caught: zero input
safe = try_await (risky 5)
print (show safe) -- Ok 10
Async TCP Server
Non-blocking TCP server in 8 lines. async_tcp_listen binds the port; async_tcp_accept waits for a client without blocking. The mio reactor handles epoll/kqueue — no OS thread is consumed per connection.
async fn handle client =
data = await (async_tcp_read client)
await (async_tcp_write client ("echo: " ++ data))
await (async_tcp_close client)
async fn main =
listener = await (async_tcp_listen 9877)
client = await (async_tcp_accept listener)
await (handle client)
-- Test: echo "hello" | nc 127.0.0.1 9877
Real Programs
HTTP Server with Concurrency
A concurrent HTTP server in 9 lines. scope starts a supervised concurrent region; spawn runs each connection handler in parallel. The server loop never blocks — each client gets its own fiber.
handle fd =
req = fd_readline fd
fd_write fd "HTTP/1.0 200 OK\r\n\r\nHello!"
fd_close fd
main = scope
listener = tcp_listen 8080
loop l =
client = tcp_accept l
spawn (handle client)
loop l
loop listener
JSON Processing
json_parse returns Ok value or Err msg. json_get key obj extracts a field — also a Result. Chaining with and_then gives a safe, composable JSON pipeline without any exception handling.
get_field key obj = unwrap (json_get key obj)
transform src =
parsed = unwrap (json_parse src)
name = get_field "name" parsed
age = get_field "age" parsed
"User: ${name}, age ${show age}"
main =
src = "{\"name\": \"Alice\", \"age\": 30}"
print (transform src)
-- User: Alice, age 30
HTTP Client
http_get returns Ok body on success or Err msg on failure. Pattern matching on the result directly — no try/catch wrapper, no status-code parsing. Works in the JIT path for high-throughput scripts.
fetch url =
result = http_get url
? result
-> Ok body -> print body
-> Err msg -> print "Error: ${msg}"
main =
fetch "http://httpbin.org/get"
List Operations
zip_with: pairwise combine
zip_with applies a binary function element-by-element across two lists — shorter list determines the length. A concise alternative to indexed loops.
main = print (zip_with (+) [1 2 3 4 5] [10 20 30 40 50])
-- [11 22 33 44 55]
dot_product xs ys = foldl (+) 0 (zip_with (*) xs ys)
main = print (dot_product [1 2 3] [4 5 6])
-- 32
zip: pair two lists
zip produces a list of pairs, stopping at the shorter input. Useful for combining parallel data without index arithmetic.
names = ["Alice" "Bob" "Carol"]
scores = [92 87 95]
ranked = zip names scores
-- [("Alice",92) ("Bob",87) ("Carol",95)]
main = print (map (\p -> "${fst p}: ${show (snd p)}") ranked)
-- ["Alice: 92" "Bob: 87" "Carol: 95"]
take / drop: list slicing
take n returns the first n elements; drop n skips them. Together they implement any slice or pagination pattern without index math.
xs = [1 2 3 4 5 6 7 8 9 10]
page n size = take size (drop (n * size) xs)
main =
print (take 3 xs) -- [1 2 3]
print (drop 7 xs) -- [8 9 10]
print (page 1 3) -- [4 5 6]
sort: natural ordering
sort works on any type with a natural order — Int, Float, String. Returns a new list; the original is unchanged.
main =
print (sort [3 1 4 1 5 9 2 6 5]) -- [1 1 2 3 4 5 5 6 9]
print (sort ["banana" "apple" "cherry"])
-- ["apple" "banana" "cherry"]
reverse
Pure list reversal. Combine with == for palindrome checks, or use as the final step in an accumulator fold.
palindrome s = s == reverse s
main =
print (palindrome "racecar") -- true
print (palindrome "hello") -- false
print (reverse [1 2 3 4 5]) -- [5 4 3 2 1]
nub: remove duplicates
nub removes duplicate elements, preserving the first occurrence. O(n²) but zero-allocation — appropriate for small lists.
tags = ["rust" "wasm" "rust" "llm" "wasm" "synoema"]
unique_tags = nub tags
-- ["rust" "wasm" "llm" "synoema"]
counts = map (\t -> (t, length (filter (\x -> x == t) tags))) unique_tags
partition: split by predicate
partition returns a pair of lists: elements satisfying the predicate, and those that don't. Single pass — more efficient than two separate filters.
Pair a b = MkPair a b
fst (MkPair a _) = a
snd (MkPair _ b) = b
result = partition even [1 2 3 4 5 6 7 8]
evens = fst result -- [2 4 6 8]
odds = snd result -- [1 3 5 7]
chunks_of: fixed-size groups
chunks_of n splits a list into non-overlapping groups of size n. The last group may be smaller than n.
matrix = chunks_of 3 [1 2 3 4 5 6 7 8 9]
-- [[1 2 3] [4 5 6] [7 8 9]]
-- process rows in batches of 5
batch_process xs = map process (chunks_of 5 xs)
intersperse: insert separators
intersperse sep xs inserts sep between each pair of elements. The string version uses intercalate to join with a string separator.
main =
print (intersperse 0 [1 2 3 4]) -- [1 0 2 0 3 0 4]
print (intercalate ", " ["a" "b" "c"]) -- "a, b, c"
csv = intercalate "," ["name" "age" "role"]
print csv -- "name,age,role"
flatten: nested list collapse
flatten collapses one level of nesting. Combine with map (i.e. concatMap) to expand and flatten in one pass.
groups = [[1 2 3] [4 5] [6 7 8 9]]
flat = flatten groups -- [1 2 3 4 5 6 7 8 9]
-- concatMap = flatten . map
expand x = [x x*2 x*3]
main = print (concatMap expand [1 2 3])
-- [1 2 3 2 4 6 3 6 9]
Math Builtins
sin / cos / pi
Transcendental functions operate on Float. pi is the built-in constant. Results are identical between interpreter and JIT.
main =
print (sin (pi / 6)) -- 0.5
print (cos 0.0) -- 1.0
print (sin (pi / 4)) -- 0.7071...
circle_area r = pi * r * r
print (circle_area 5.0) -- 78.539...
mean: arithmetic average
mean computes the arithmetic mean of a Float list. Returns 0.0 on an empty list. Works in both interpreter and JIT.
scores = [82.0 91.0 74.0 88.0 95.0 79.0]
avg = mean scores -- 84.833...
-- weighted mean: zip weights and values
weighted_mean ws vs =
total_w = foldl (+) 0.0 ws
total_v = foldl (+) 0.0 (zip_with (*) ws vs)
total_v / total_w
stddev: sample standard deviation
stddev uses Bessel's correction (n−1 denominator) matching R's sd(). Returns 0.0 for lists with fewer than 2 elements.
temperatures = [22.1 23.4 21.8 24.0 22.7]
spread = stddev temperatures -- ≈ 0.88
-- coefficient of variation (relative spread)
cv xs = stddev xs / mean xs * 100.0
exp / ln: exponential and natural log
exp x computes e^x; ln x computes the natural logarithm. They are inverses: ln (exp x) == x.
main =
print (exp 1.0) -- 2.71828... (e)
print (ln (exp 3.0)) -- 3.0
print (exp 0.0) -- 1.0
-- continuous compounding: A = P * exp(r * t)
compound p r t = p * exp (r * t)
atan2: two-argument arctangent
atan2 y x returns the angle in radians in the correct quadrant. Essential for vector direction, physics, and coordinate transformations.
-- angle of vector (3, 4)
angle = atan2 4.0 3.0 -- ≈ 0.927 radians
-- convert to degrees
to_deg r = r * 180.0 / pi
main = print (to_deg (atan2 1.0 1.0)) -- 45.0
log10: base-10 logarithm
log10 x is the base-10 log. Use log2 for bit-width calculations. For an arbitrary base: log_b b x = ln x / ln b.
main =
print (log10 1000.0) -- 3.0
print (log10 100.0) -- 2.0
print (log2 1024.0) -- 10.0
-- decibel conversion
db power_ratio = 10.0 * log10 power_ratio
Contracts
requires: precondition guard
A requires annotation on a function asserts a precondition on the inputs. Violated at call-site → runtime error with the contract message. Extracted to docs by synoema doc --contracts.
sqrt_safe : Float -> Float
--- requires: x >= 0.0
sqrt_safe x = x ** 0.5
-- Calling sqrt_safe (-1.0) raises:
-- contract violation: requires: x >= 0.0
ensure: postcondition assertion
An ensure clause asserts a property of the return value. The keyword result refers to the actual returned value. It is written as an indented line under the type signature.
abs_val : Int -> Int
ensure result >= 0
abs_val n = ? n < 0 -> n * (-1) : n
normalize : List Float -> List Float
ensure length result == length xs
normalize xs = map (\x -> x / mean xs) xs
requires + ensure: full contract
Combining both clauses gives machine-verifiable pre/post conditions. The compiler extracts the full contract into documentation.
divide : Int -> Int -> Int
requires b != 0
ensure result * b == a
divide a b = a / b
bsearch : List Int -> Int -> Result Int String
requires sort xs == xs
ensure is_ok result -> index (unwrap result) xs == target
bsearch xs target = go xs target 0 (length xs - 1)
contract extraction CLI
Run synoema doc --contracts file.sno to generate a compact table of all annotated functions. Works with MCP tool doc_query for agent-side retrieval.
-- Run: synoema doc --contracts examples/bsearch.sno
-- Output:
-- | Function | Requires | Ensure |
-- |----------|----------------------|---------------------------|
-- | bsearch | sort xs == xs | index (unwrap r) xs == t |
-- | divide | b != 0 | result * b == a |
IoT Rules
Threshold rule with contract
The IoT rules subset uses integer arithmetic only (no Float) for Tier 0 MCU targets. The requires clause constrains sensor range; ensure asserts output is binary.
fan_control : Int -> Int
requires temp >= -40 && temp <= 125
ensure result == 0 || result == 1
fan_control temp = ? temp > 30 -> 1 : 0
main =
print (fan_control 35) -- 1 (fan on)
print (fan_control 22) -- 0 (fan off)
Hysteresis rule: dual-threshold
Hysteresis prevents rapid on/off switching near the threshold. The rule only changes state when crossing the high threshold on the way up or the low threshold on the way down.
heater_hyst : Int -> Int -> Int
requires low < high
heater_hyst state temp =
? state == 0 && temp > 22 -> 1
: ? state == 1 && temp < 18 -> 0
: state
main =
s0 = heater_hyst 0 25 -- 1 (turn on at 25)
s1 = heater_hyst 1 20 -- 1 (stay on, above low threshold)
s2 = heater_hyst 1 17 -- 0 (turn off below 18)
Interlock rule: multi-sensor safety
Interlocks require multiple conditions to be simultaneously true before allowing an action. Critical in industrial safety — no single sensor can trigger an unsafe state.
valve_interlock : Int -> Int -> Int -> Int
requires pressure >= 0 && flow >= 0
ensure result == 0 || result == 1
valve_interlock pressure flow override =
? override == 1 -> 0
: ? pressure < 50 && flow > 10 -> 1
: 0
Timer / accumulator rule
Timer rules accumulate ticks and trigger after a threshold is crossed. Integer-only, safe for Tier 0 MCUs with no floating-point unit.
inactivity_alert : Int -> Int -> Int
requires ticks >= 0 && threshold > 0
ensure result == 0 || result == 1
inactivity_alert ticks threshold =
? ticks >= threshold -> 1 : 0
-- step the timer: reset on motion, increment on idle
step motion ticks = ? motion == 1 -> 0 : ticks + 1
String & Text
Word frequency counter
Build a frequency map by folding over a list of words. map_get returns a default value on missing key — no explicit "not found" handling needed.
count_words words =
foldl (\m w -> map_insert w (map_get w 0 m + 1) m) map_empty words
main =
text = ["the" "cat" "sat" "on" "the" "mat" "the"]
counts = count_words text
print (map_lookup "the" counts) -- Ok 3
print (map_lookup "cat" counts) -- Ok 1
ROT13 transformation
A character-level string transformation using str_map. Demonstrates that ROT13 is self-inverse: applying it twice returns the original.
rot13_char c =
? c >= 65 && c <= 90 -> ((c - 65 + 13) % 26) + 65
: ? c >= 97 && c <= 122 -> ((c - 97 + 13) % 26) + 97
: c
rot13 s = str_map rot13_char s
main =
enc = rot13 "Hello, World!" -- "Uryyb, Jbeyq!"
print (rot13 enc) -- "Hello, World!"
intercalate: join with separator
intercalate sep xss joins a list of strings (or lists) with a separator — the string equivalent of Python's str.join.
main =
print (intercalate ", " ["Alice" "Bob" "Carol"])
-- "Alice, Bob, Carol"
path_parts = ["usr" "local" "bin" "sno"]
print (intercalate "/" path_parts)
-- "usr/local/bin/sno"
csv_row fields = intercalate "," fields
Palindrome check
A one-liner using reverse and structural equality. Works for both strings and lists.
is_palindrome s = s == reverse s
main =
print (is_palindrome "racecar") -- true
print (is_palindrome "level") -- true
print (is_palindrome "synoema") -- false
print (is_palindrome [1 2 3 2 1]) -- true
Type Patterns
Maybe: optional values without null
Synoema has no null. Optional values are expressed with Just x / None. Chain optional operations with map_maybe and from_maybe — no null checks, no exceptions.
Maybe a = Just a | None
safe_head [] = None
safe_head (x:_) = Just x
safe_div _ 0 = None
safe_div a b = Just (a / b)
main =
print (from_maybe (-1) (safe_div 10 2)) -- 5
print (from_maybe (-1) (safe_div 10 0)) -- -1
print (safe_head [3 1 4]) -- Just 3
Mutual recursion: even / odd
Two functions that call each other. Synoema resolves mutually recursive definitions in the same file without any forward-declaration syntax.
is_even 0 = true
is_even n = is_odd (n - 1)
is_odd 0 = false
is_odd n = is_even (n - 1)
main =
print (is_even 10) -- true
print (is_odd 7) -- true
print (map is_even [0 1 2 3 4 5])
-- [true false true false true false]