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
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"