Language Reference
Complete guide to writing Synoema programs
Mental Model
If you know Python or Haskell, override these habits first:
| Instead of | Write in Synoema | Why |
def f(x): | f x = body | No def keyword |
if c then x else y | ? c -> x : y | 3 tokens instead of keywords |
return x | (just the expression) | Last expression = result |
[1, 2, 3] | [1 2 3] | Space-separated, no commas |
s1 + s2 (strings) | s1 ++ s2 | + is for numbers only |
f"x={x}" | "x=${x}" | ${} interpolation |
Core axioms:
- No
def, no return — name args = body; last expression is the result
- Offside rule — indentation defines blocks (like Python)
- Immutable — all bindings are immutable
- Strict — eager evaluation, left-to-right (not lazy like Haskell)
- Types inferred — annotations are optional:
f : Int -> Int
Types
Hindley-Milner type inference. You rarely need to write types.
Primitive Types
| Type | Examples | Notes |
Int | 42, -7, 0 | 63-bit signed integer |
Float | 3.14, 2.0 | 64-bit IEEE 754 |
Bool | true, false | |
String | "hello", "x=${x}" | With ${} interpolation |
() | () | Unit type |
Composite Types
| Type | Example | Notes |
List a | [1 2 3], [] | Homogeneous, space-separated |
| Records | {x = 3, y = 4} | Structural (row polymorphism) |
| ADT | Maybe a = Just a | None | Algebraic data types |
Result a e | Ok 42, Err "fail" | From prelude |
Chan a | chan () | Typed channel |
Type Aliases
type Pos = {x : Int, y : Int}
type Transform = Int -> Int
type Pair a b = {fst : a, snd : b}
Functions & Pattern Matching
Functions are defined with name args = body. Multiple equations define pattern matching (first match wins):
-- Simple function
double x = x * 2
-- Pattern matching: multiple equations
fac 0 = 1
fac n = n * fac (n - 1)
-- Wildcard pattern
describe 0 = "zero"
describe _ = "other"
-- Cons pattern (parentheses required!)
head (x:_) = x
tail (_:xs) = xs
isEmpty [] = true
isEmpty _ = false
-- Lambda
square = \x -> x * x
-- Type annotation (optional)
add : Int -> Int -> Int
add x y = x + y
Verification Contracts
clamp : Int -> Int
requires x >= 0
requires x <= 1000
ensure result >= 1
ensure result <= 100
clamp x = ? x < 1 -> 1 : ? x > 100 -> 100 : x
requires — precondition, checked before body executes
ensure — postcondition, checked after return; result in scope
- Violation → runtime error
Operators
Precedence from lowest to highest (13 levels). Every operator = 1 BPE token.
| Op | Meaning | Assoc |
<- | IO/monadic bind | right |
|> | Pipe: x |> f = f x | left |
|| | Logical or | left |
&& | Logical and | left |
== != | Equality | none |
< > <= >= | Comparison | none |
++ | String/list concat | right |
+ - | Add/sub | left |
* / % | Mul/div/mod | left |
** | Power | right |
>> | Compose: f >> g | right |
- (prefix) | Negate | — |
. | Field access | left |
| juxtaposition | Function application f x | left |
Control Flow
Conditional: ? -> :
The ternary operator replaces if/else:
abs x = ? x < 0 -> -x : x
-- Multi-branch chain
fizzbuzz n =
? n % 15 == 0 -> "FizzBuzz"
: ? n % 3 == 0 -> "Fizz"
: ? n % 5 == 0 -> "Buzz"
: show n
Local Bindings
hyp a b =
a2 = a * a
b2 = b * b
a2 + b2
Pipes
result = [1 2 3 4 5 6 7 8 9 10]
|> filter (\x -> x % 2 == 0)
|> map (\x -> x * x)
|> sum
-- result = 220
Sequencing
-- Use ; to sequence side effects
main = print "a" ; print "b" ; print "c"
Lists
xs = [1 2 3 4 5] -- space-separated, NO commas
empty = []
r = [1..10] -- range (inclusive)
both = [1 2] ++ [3 4] -- concatenation: [1 2 3 4]
ys = 1 : [2 3] -- cons (prepend): [1 2 3]
-- List comprehension
evens = [x | x <- [1..20], x % 2 == 0]
pairs = [[a b] | a <- [1 2], b <- ["x" "y"]]
-- Pattern matching
only [x] = x -- exactly one element
sum3 [a b c] = a + b + c -- exactly three
Gotcha: : is both cons and else-branch in ?->:. In then-branch, parenthesize cons: ? cond -> (x:xs) : rest
Records
-- Create
pt = {x = 3, y = 4}
-- Field access
px = pt.x -- 3
-- Nested
circle = {center = {x = 0, y = 0}, radius = 5}
cx = circle.center.x -- 0
-- Punning: {x, y} = {x = x, y = y}
point x y = {x, y}
-- Pattern matching (destructure)
dist {x, y} = x * x + y * y
-- Record update (spread syntax)
move_x pt dx = {...pt, x = pt.x + dx}
Algebraic Data Types
-- Define sum types
Maybe a = Just a | None
Shape = Circle Float | Rect Float Float | Point
-- Construct
x = Just 42
s = Circle 3.0
-- Pattern match
fromMaybe def None = def
fromMaybe _ (Just x) = x
area (Circle r) = r * r
area (Rect w h) = w * h
area Point = 0
-- Derive
Color = Red | Green | Blue derive (Show, Eq, Ord)
Type Classes
-- Define a trait
trait Show a
show : a -> String
-- Implement for a type
Color = Red | Green | Blue
impl Show Color
show Red = "red"
show Green = "green"
show Blue = "blue"
-- Constrained implementation
impl Show (Maybe a) ? Show a
show None = "None"
show (Just x) = "Just " ++ show x
Modules
-- Single-file module
mod Math
square x = x * x
pi = 3.14159
use Math (square pi) -- selective import
use Math (*) -- wildcard import
-- Multi-file
-- main.sno
import "math.sno"
use Math (square)
main = square 5
Error Handling
-- Result type (from prelude)
Result a e = Ok a | Err e
safe_div x 0 = Err "division by zero"
safe_div x y = Ok (x / y)
-- Chain with and_then
parse_and_double input =
parse input |> and_then (\n -> safe_div n 2)
-- Monadic bind with <-
main =
a <- safe_div 10 2
b <- safe_div a 3
Ok (a + b)
-- Unrecoverable error
head [] = error "empty list"
head (x:_) = x
String Interpolation
name = "world"
msg = "Hello ${name}" -- "Hello world"
sum = "${a} + ${b} = ${a + b}" -- expressions allowed
esc = "\$ is literal dollar" -- escape with \$
Standard Library
List Operations
| Function | Type | Description |
length | [a] -> Int | List length |
head | [a] -> a | First element |
tail | [a] -> [a] | All but first |
map | (a -> b) -> [a] -> [b] | Transform each |
filter | (a -> Bool) -> [a] -> [a] | Keep matching |
foldl | (b -> a -> b) -> b -> [a] -> b | Left fold |
zip | [a] -> [b] -> [(a,b)] | Pair elements |
index | [a] -> Int -> a | 0-based index |
take | Int -> [a] -> [a] | First n elements |
drop | Int -> [a] -> [a] | Skip first n |
reverse | [a] -> [a] | Reverse |
sum | [Int] -> Int | Sum elements |
sort | [a] -> [a] | Natural ordering |
any | (a -> Bool) -> [a] -> Bool | Any match? |
all | (a -> Bool) -> [a] -> Bool | All match? |
find | (a -> Bool) -> [a] -> Result a String | First match or Err |
elem | a -> [a] -> Bool | Membership |
flatten | [[a]] -> [a] | Flatten nested |
partition | (a -> Bool) -> [a] -> Pair [a] [a] | Split by predicate |
String Operations
| Function | Type | Description |
str_len | String -> Int | String length |
str_slice | String -> Int -> Int -> String | Substring (from, to) |
str_find | String -> String -> Int -> Int | Find substring (-1 = not found) |
str_starts_with | String -> String -> Bool | Prefix check |
str_trim | String -> String | Trim whitespace |
split | String -> String -> [String] | Split by separator |
join | String -> [String] -> String | Join with separator |
replace | String -> String -> String -> String | Replace all occurrences |
upper | String -> String | Uppercase |
lower | String -> String | Lowercase |
words | String -> [String] | Split on whitespace |
lines | String -> [String] | Split on newlines |
chars | String -> [String] | Characters as list |
json_escape | String -> String | Escape for JSON |
Math
| Function | Type | Description |
sqrt | Float -> Float | Square root |
floor | Float -> Float | Round down |
ceil | Float -> Float | Round up |
round | Float -> Float | Round to nearest |
abs | Int -> Int | Absolute value |
min | a -> a -> a | Minimum |
max | a -> a -> a | Maximum |
even / odd | Int -> Bool | Parity check |
Type Conversion
| Function | Type | Description |
show | a -> String | Convert any value to string |
to_int | String -> Result Int String | Parse string to integer |
to_float | String -> Result Float String | Parse string to float |
Result Combinators (prelude)
| Function | Type | Description |
map_ok | (a -> b) -> Result a e -> Result b e | Transform Ok value |
map_err | (e -> f) -> Result a e -> Result a f | Transform Err value |
unwrap | Result a e -> a | Extract Ok (panic on Err) |
unwrap_or | a -> Result a e -> a | Extract with default |
is_ok / is_err | Result a e -> Bool | Check variant |
and_then | (a -> Result b e) -> Result a e -> Result b e | Chain operations |
error | String -> a | Runtime panic |
JSON
| Function | Type | Description |
json_parse | String -> Result JsonValue String | Parse JSON string |
json_get | String -> JsonValue -> Result JsonValue String | Get field from object |
json_escape | String -> String | Escape string for JSON |
I/O & Networking
Console
| Function | Type | Description |
print | a -> () | Print with newline |
readline | String | Read line from stdin |
File I/O
| Function | Type | Description |
file_read | String -> String | Read entire file |
fd_open | String -> Int | Open for reading |
fd_open_write | String -> Int | Open for writing |
fd_readline | Int -> String | Read one line |
fd_write | Int -> String -> () | Write string |
fd_close | Int -> () | Close handle |
fd_popen | String -> Int | Run command, get stdout |
TCP Networking
| Function | Type | Description |
tcp_listen | Int -> Int | Listen on port |
tcp_accept | Int -> Int | Accept connection |
-- Minimal HTTP server
handle fd =
req = fd_readline fd
fd_write fd "HTTP/1.0 200 OK\r\n\r\nHello!"
fd_close fd
main =
listener = tcp_listen 8080
loop l = handle (tcp_accept l) ; loop l
loop listener
HTTP Client
| Function | Type | Description |
http_get | String -> Result String String | GET request |
http_post | String -> String -> Result String String | POST with body |
HTTP only — no HTTPS. Use fd_popen "curl https://..." for HTTPS.
Environment
| Function | Type | Description |
env_or | String -> String -> String | Get env var with default |
args | [String] | Command-line arguments |
Concurrency
-- Structured concurrency
main = scope {
c = chan ()
spawn (send c 42)
print (recv c) -- prints 42
}
-- Parallel map
result = pmap (\x -> x * x) [1 2 3 4 5]
| Function | Type | Description |
scope { ... } | a | Join all spawned threads before returning |
spawn expr | () | Run expression in new OS thread |
chan | Chan a | Create typed channel |
send | Chan a -> a -> () | Send (non-blocking) |
recv | Chan a -> a | Receive (blocking) |
pmap | (a -> b) -> [a] -> [b] | Parallel map |
Time
| Function | Type | Description |
now | () -> Int | Current Unix time (ms) |
elapsed | Int -> Int | Milliseconds since timestamp |
sleep | Int -> () | Sleep for N milliseconds |
Testing
-- Doctests (in doc comments)
--- Compute factorial.
--- example: fact 5 == 120
fact 0 = 1
fact n = n * fact (n - 1)
-- Unit tests
test "base case" = fact 0 == 1
test "fact 10" = fact 10 == 3628800
-- Property tests
test "reverse involution" =
prop xs -> reverse (reverse xs) == xs
test "positive factorial" =
prop n -> fact n >= 1 when n >= 0 && n <= 10
Run: synoema test file.sno or synoema test directory/
CLI Reference
| Command | Description |
synoema run file.sno | Run with interpreter |
synoema jit file.sno | Run with Cranelift JIT (~4.4x faster) |
synoema eval "expr" | Evaluate expression |
synoema build file.sno | Compile to bytecode |
synoema test dir/ | Run tests (doctests + unit + property) |
synoema doc file.sno | Generate documentation |
synoema doc --contracts file.sno | Contract summary table |
synoema watch run file.sno | Watch + re-run on change |
synoema init myapp | Scaffold new project |
synoema install | Install to ~/.synoema/bin |
synoema --errors json run file.sno | JSON error output for LLM |
Interpreter vs JIT
| Feature | run (interpreter) | jit (Cranelift) |
| All language features | Yes | Yes |
| File/Network I/O | Yes | No |
| Concurrency (chan/send/recv) | Yes | No |
| Constant folding / DCE | — | Yes |
| Speed | 1x | ~4.4x |
Use run for development and I/O. Use jit for performance.