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"