Synoema

Language Reference

Complete Synoema reference — syntax, types, stdlib, concurrency

Contents

§1 Mental Model

If you know Python or Haskell, unlearn these habits:

Instead of (Python/Haskell)Write in SynoemaWhy
def f(x):f x = bodyNo def keyword
if c then x else y? c -> x : y3 tokens instead of keywords
return x(just an expression)Last expression = result
[1, 2, 3][1 2 3]Space-separated, no commas
let x = e in bodyIndented x = e, then bodyOffside rule
data T = A | BT = A | BNo keyword — name = constructors
class / instancetrait / impl
import Muse M (f g)Selective import
s1 + s2 (strings)s1 ++ s2+ is numbers only
f"x={x}""x=${x}"Interpolation via ${}

Key principles:

  • No def, no returnname arguments = body; last expression = result
  • Offside rule — indentation defines blocks (like Python)
  • Everything is immutable — all bindings are immutable
  • Strict evaluation — eager left-to-right evaluation
  • Types are inferred — annotations are optional
  • Define before use — functions must be declared before they are used
  • Multi-equation functions at top level only

§2 Comments

-- Single-line comment (two dashes)

--- Doc comment (three dashes)
--- Used by: sno doc file.sno
--- Lines starting with > are doctests
---
--- > double 5
--- 10
double x = x * 2

{- Multi-line comment
   Can span multiple lines.
-}

§3 Functions and Bindings

-- Function: name arguments = body (last expression = result)
double x = x * 2

-- Multiple equations = pattern matching (first match wins)
-- Top-level module only!
fac 0 = 1
fac n = n * fac (n - 1)

-- Wildcard _ matches anything
describe 0 = "zero"
describe _ = "other"

-- Local bindings via indentation (offside rule)
circleArea r =
  pi_val = 3.14159        -- local variable
  r2     = r * r
  pi_val * r2             -- result

-- Lambda: \arguments -> body
square = \x -> x * x
add    = \x y -> x + y

-- Pipe |>: x |> f = f x (reads as pipeline)
result = [1..10]
  |> filter (\x -> x % 2 == 0)
  |> map (\x -> x * x)
  |> sum
-- result = 220

-- Currying (partial application)
addFive  = add 5          -- function Int -> Int
results  = map (add 10) [1 2 3]   -- [11 12 13]

§4 Data Types

Hindley-Milner type inference — annotations are optional but useful as documentation.

Primitive types

TypeExamplesNotes
Int42, -7, 063-bit signed integer
Float3.14, -0.564-bit IEEE 754
Booltrue, false
String"hello", "x=${x}"UTF-8, interpolation via ${}
()()Unit — no meaningful result
Nat5, 0nNon-negative integer
Rational1/2, 3/4Exact fraction without float rounding
Char'A', '\n'Unicode character
Bytesb"hello"Binary data
-- Float and Int do not mix -- explicit conversion required
area = pi * to_float 5 * to_float 5   -- Float * Float * Float

-- Rational: exact fraction arithmetic
sum_thirds = 1/3 + 2/3    -- exactly 1 (not 0.9999...)

-- String interpolation
name = "World"
s = "Hello, ${name}!"     -- "Hello, World!"
n = "2 + 2 = ${2 + 2}"   -- "2 + 2 = 4"

Composite types

TypeExampleNotes
List a[1 2 3], []Homogeneous, space-separated
Record{x=3, y=4}Named fields
ADTColor = Red | Green | BlueAlgebraic types — no keyword
Result a eOk 42, Err "fail"From prelude
Maybe aJust 42, NoneFrom prelude

§5 Operators

OperatorMeaningExample
<-IO bindline <- readline
|>pipe: x |> f = f x[1 2 3] |> sum
||logical ORa || b
&&logical ANDa && b
== !=equality / inequalityx == 0
< > <= >=comparisonx > 0
++string and list concatenation"a" ++ "b"
+ -addition / subtractionx + 1
* / %multiply / divide / remainderx * 2, 10 % 3
**Float exponentiation2.0 ** 0.5
>>function compositiondouble >> show
! -NOT / unary minus!flag, -x
.record field accesspoint.x
f xfunction applicationsqrt 2.0

Every operator is exactly 1 BPE token (cl100k_base).

-- Int / Int = Int (integer division)
quot = 10 / 3    -- 3  (not 3.333...)
rem  = 10 % 3   -- 1  (remainder)

-- Cons operator : (prepend to list)
lst = 1 : [2 3]  -- [1 2 3]
-- In patterns requires parens: (x:xs)

§6 Conditionals and Control Flow

-- Conditional expression: ? condition -> then : else
-- This is an EXPRESSION, not a statement -- it returns a value
abs x = ? x >= 0 -> x : -x

-- Chained conditions (elif equivalent)
classify x =
  ? x < 0   -> "negative"
  : ? x == 0 -> "zero"
  :             "positive"

-- Pattern matching via multiple equations (idiomatic)
fac 0 = 1
fac n = n * fac (n - 1)

-- Patterns: literal, wildcard _, cons (x:xs), constructor
head (x:_)  = x          -- first element
tail (_:xs) = xs         -- tail

-- Cons patterns must be in parens!
-- CORRECT:  head (x:_) = x
-- WRONG:    head x:_ = x  ← parse error

§7 Lists

-- Creation
empty  = []
nums   = [1 2 3 4 5]          -- space-separated, no commas!
strs   = ["a" "b" "c"]
ranges = [1..10]               -- [1 2 3 4 5 6 7 8 9 10]

-- Cons: prepend element
lst = 0 : [1 2 3]             -- [0 1 2 3]

-- Pattern matching (patterns in parens!)
myHead (x:_)  = x
myTail (_:xs) = xs
myLen []     = 0
myLen (_:xs) = 1 + myLen xs

-- Standard library
map (\x -> x * 2) [1..5]      -- [2 4 6 8 10]
filter (\x -> x > 3) [1..5]   -- [4 5]
foldl (+) 0 [1..100]          -- 5050
take 3 [1..10]                -- [1 2 3]
drop 3 [1..10]                -- [4 5 6 7 8 9 10]
reverse [1 2 3]               -- [3 2 1]
sort [3 1 2]                  -- [1 2 3]
zip [1 2 3] ["a" "b" "c"]     -- [(1,"a") (2,"b") (3,"c")]
concat [[1 2] [3 4]]          -- [1 2 3 4]
any (\x -> x > 5) [1..10]    -- true
all (\x -> x > 0) [1..5]     -- true
length [1 2 3]                -- 3
join ", " ["a" "b" "c"]       -- "a, b, c"
elem 3 [1 2 3 4]              -- true

§8 Records

-- Type definition
type Point = {x: Int, y: Int}

-- Creation
p = {x=10, y=20}

-- Field access
px = p.x    -- 10
py = p.y    -- 20

-- Update (immutable -- creates a new record)
p2 = {p | x=15}           -- {x=15, y=20}
p3 = {p | x=15, y=25}     -- {x=15, y=25}

-- Pattern matching on records
showPoint {x=px, y=py} = "Point(${show px}, ${show py})"

-- Nested records
type Rect = {topLeft: Point, bottomRight: Point}
r = {topLeft={x=0, y=0}, bottomRight={x=100, y=100}}
width = r.bottomRight.x - r.topLeft.x    -- 100

§9 ADT — Algebraic Data Types

ADTs are defined as a union of constructors. No keyword — just Name = Constructor | Constructor. The type keyword is for type aliases only (see §4).

-- Definition: no data, no type keyword
Color = Red | Green | Blue

Shape
  = Circle Float           -- one argument
  | Rectangle Float Float  -- two arguments
  | Point                  -- no arguments

-- Parameterized types
Maybe a = Just a | None    -- None, not Nothing! (from prelude)
Result a e = Ok a | Err e

-- Usage
c = Circle 5.0
r = Rectangle 10.0 20.0

-- Pattern matching in function heads
area (Circle r)       = 3.14159 * r * r
area (Rectangle w h)  = w * h
area Point            = 0.0

-- Maybe: use None, not Nothing
fromMaybe def None     = def
fromMaybe _   (Just x) = x

-- Result
safe_div _ 0 = Err "division by zero"
safe_div a b = Ok (a / b)

-- Derive typeclasses automatically
Status = Active | Inactive | Pending derive (Show, Eq, Ord)

-- Tree (recursive polymorphic ADT)
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

Important gotchas:

  • Constructors start with uppercase: Just, None, Circle, Ok, Err
  • Variables start with lowercase: x, r, result
  • The prelude defines Maybe a = Just a | None — use None, never Nothing
  • Recursive ADTs like Tree work without annotations — HM infers everything
  • Cons patterns require parentheses: (x:xs) not x:xs

§10 Modules and Imports

-- Import specific functions
use Math (sqrt pow sin cos)

-- Import everything from a module
use List (*)

-- File math_utils.sno automatically creates module MathUtils

-- IMPORTANT: define before use -- functions must be declared before use
-- No forward declarations

§11 Traits and Implementations

-- Trait definition (typeclass / interface)
trait Displayable a =
  display : a -> String

-- Implementation for a concrete type
impl Displayable Int =
  display x = "Int(${show x})"

impl Displayable Bool =
  display true  = "yes"
  display false = "no"

-- Usage with constraints
printAny : Displayable a => a -> ()
printAny x = print (display x)

§12 Input/Output (IO)

-- Output
print "Hello"                     -- print + newline

-- File operations
content = file_read "data.txt"    -- read entire file as String
file_write "out.txt" "content"    -- write to file
exists  = file_exists "data.txt"  -- Bool

-- Subprocess (run a command)
pfd  = fd_popen "ls -la"          -- open process for reading
line = fd_readline pfd            -- read a line
fd_close pfd                      -- close

-- Environment
val = env_or "MY_VAR" "default"  -- env var or default

-- Network (TCP)
listener = tcp_listen 3000        -- open port
client   = tcp_accept listener    -- wait for connection
req      = fd_readline client     -- read request
fd_write client "response"        -- send response
fd_close client

§13 Strings — Standard Library

-- Concatenation and interpolation
s = "Hello" ++ ", " ++ "World"   -- "Hello, World"
n = 42
s2 = "n = ${n}"                  -- "n = 42"

str_len "Hello"                   -- 5
str_slice "Hello World" 6 11      -- "World"
str_find "Hello World" "World" 0  -- 6  (-1 if not found)
str_starts_with "Hello" "He"      -- true
str_ends_with "Hello" "lo"        -- true
replace "Hello World" "World" "Synoema"  -- "Hello Synoema"
str_split "a,b,c" ","            -- ["a" "b" "c"]
join ", " ["a" "b" "c"]          -- "a, b, c"
str_upper "hello"                 -- "HELLO"
str_lower "HELLO"                 -- "hello"
str_trim "  hello  "             -- "hello"
show 42                           -- "42" (any type to String)
to_int "42"                       -- Just 42 / None

§14 Math Builtins

15 built-in math functions. All operate on Float.

FunctionDescriptionExample
sin xSine (radians)sin 0.0 → 0.0
cos xCosine (radians)cos 0.0 → 1.0
tan xTangenttan (pi/4.0) → 1.0
asin xArcsineasin 1.0 → π/2
acos xArccosineacos 0.0 → π/2
atan xArctangentatan 1.0 → π/4
atan2 y x2D arctangent (sign-aware)atan2 1.0 1.0 → π/4
exp xe^xexp 1.0 → 2.718...
ln xNatural logarithmln e → 1.0
log10 xBase-10 logarithmlog10 100.0 → 2.0
log2 xBase-2 logarithmlog2 8.0 → 3.0
piπ = 3.14159...circleArea r = pi * r * r
ee = 2.71828...compound = e ** (rate * time)
mean xsArithmetic meanmean [1.0 2.0 3.0] → 2.0
stddev xsSample std. dev. (n−1)stddev [1.0 2.0 3.0] → 1.0
-- Degrees to radians
toRad deg = deg * pi / 180.0

-- sin(90deg) = 1.0
val = sin (toRad 90.0)    -- 1.0

-- Circle area
circleArea r = pi * r * r

-- Distance between two points
distance x1 y1 x2 y2 =
  dx = x2 - x1
  dy = y2 - y1
  sqrt (dx*dx + dy*dy)

-- Statistics
data_pts = [1.0 2.0 3.0 4.0 5.0]
avg = mean   data_pts    -- 3.0
dev = stddev data_pts    -- 1.581...

§15 Concurrency

Structured parallelism with data isolation. Threads do not share mutable state.

-- scope: waits for ALL spawned threads inside
-- spawn: run in a separate OS thread
main =
  scope
    spawn (print "Worker 1")    -- parallel
    spawn (print "Worker 2")    -- parallel
  -- both have finished here

-- Channels: pass data between threads
main2 =
  ch = chan
  scope
    spawn (send ch 42)    -- producer
    val = recv ch         -- consumer (= 42)
    print "Got: ${show val}"

-- Bounded channel (backpressure)
ch = bounded_chan 10           -- 10-element buffer

-- Non-blocking
sent = try_send ch 42          -- Bool
val  = try_recv ch             -- Maybe a

-- Timeout
result = recv_timeout ch 500   -- Maybe a (500 ms)

-- select: first ready from multiple channels
pair  = select [ch1 ch2 ch3]  -- (idx, value)
idx   = fst pair
value = snd pair

-- pmap: parallel map (CPU-bound)
results = pmap heavy_fn [1..10000]   -- all CPU cores

§16 Async/Await

Synoema has native async/await support (Phase D). Async functions return a Task a — a deferred computation. The JIT compiles async functions into stackless state machines. The interpreter uses OS threads.

-- Declare: async fn name = body
async fn fetch_data url =
  content = await (async_read url)
  upper content

-- Call: await e where e : Task a
main =
  result = await fetch_data "/tmp/data.txt"
  print result

-- Multiple awaits (JIT: stackless state machine)
async fn pipeline src dst =
  raw  = await (async_read src)
  _    = await (async_write dst (upper raw))
  done = await (async_read dst)
  done

Async builtins

BuiltinTypeDescription
async_sleep msInt → Task UnitNon-blocking sleep (milliseconds)
async_read pathString → Task StringAsync file read
async_write path sString → String → Task IntAsync file write; returns bytes written
async_tcp_connect host portString → Int → Task IntConnect to TCP host; returns socket fd
async_tcp_read fdInt → Task StringAsync TCP read
async_tcp_write fd sInt → String → Task IntAsync TCP write
async_tcp_listen portInt → Task IntListen on port; returns listener fd
async_tcp_accept fdInt → Task IntAccept connection; returns client fd
async_tcp_close fdInt → Task UnitClose socket

Error handling and cancellation (Phase E)

-- scope_result: capture panic/error as Result
async fn risky =
  await (async_sleep 10)
  error "something went wrong"

main =
  r = scope_result (\() -> await risky)
  ? is_ok r -> print "ok"
  : print ("failed: " ++ show (unwrap_err r))

-- Cancellation
token = new_cancel_token ()
-- ... in another thread: cancel token
result = with_cancel token some_task   -- Maybe a (None if cancelled)

-- Timeout
result2 = await_with_timeout 500 some_task  -- Maybe a (None if timed out)

-- try_await: await without propagating panics
safe = try_await risky_task  -- Result a Error

Task combinators

-- race: first task to complete wins
winner = race [task1 task2 task3]

-- gather: run all in parallel, wait for all
results = gather [fetch_a fetch_b fetch_c]   -- List a (ordered)

Reactor (Phase G): The async runtime uses mio (epoll/kqueue) for socket I/O and a timer wheel for sleep. A bounded file-I/O pool handles async_read/async_write (4 workers, configurable via SNO_FILE_IO_THREADS).

§17 Verification Contracts

-- requires: precondition (checked BEFORE execution)
-- ensure: postcondition (checked AFTER, result = returned value)

safe_div : Int -> Int -> Result Int String
  requires b != 0           -- b must not be zero
  ensure is_ok result       -- result is always Ok
safe_div a b = ? b == 0 -> Err "division by zero" : Ok (a / b)

-- Multiple contracts
clamp : Int -> Int -> Int -> Int
  requires lo <= hi
  ensure lo <= result
  ensure result <= hi
clamp lo hi x =
  ? x < lo -> lo
  : ? x > hi -> hi
  : x

-- Check without running
-- sno check file.sno
-- Check + run with timeout + JSON output
-- sno verify file.sno
-- Contract documentation
-- sno doc --contracts file.sno

§18 Testing

--- Doctests in doc comments (triple dash)
--- > square 4
--- 16
--- > square 0
--- 0
square x = x * x

-- Unit tests
test "fac base"   = fac 0 == 1
test "fac five"   = fac 5 == 120
test "sort works" = sort [3 1 2] == [1 2 3]

-- Property-based tests
test "reverse"    = prop xs -> reverse (reverse xs) == xs
test "sort idem"  = prop xs -> sort (sort xs) == sort xs
sno test file.sno         -- all tests in file
sno test examples/        -- recursively in directory
sno test --doctest f.sno  -- doctests only

§19 Error Handling

-- Result: for recoverable errors
parse_int : String -> Result Int String
parse_int s =
  n = to_int s
  ? is_just n -> Ok (unwrap n)
  : Err "Not a valid integer: ${s}"

-- Chain with and_then
result = parse_int "42" |> and_then (\n -> Ok (n * 2))   -- Ok 84

-- error: for unrecoverable errors (halts the program)
assertPositive n =
  ? n > 0 -> n
  : error "Expected positive, got ${show n}"

-- JSON errors for LLM tools
-- sno --errors json run file.sno

§20 Common Compiler Errors

MessageCause and fix
parse error: unexpected tokenSyntax broken. Check parens around cons patterns: (x:xs), not x:xs
type error: expected T but got UType mismatch. Check argument types. Int and Float do not mix — use to_float
unbound variable: nameVariable not declared OR declared AFTER use (define-before-use)
non-exhaustive patternsPattern matching does not cover all cases. Add _ or all ADT constructors
occurs check: infinite typeRecursive type without annotation. Add an explicit type annotation
requires violationContract precondition violated. Check arguments before calling
parse error: multi-equation def in letMulti-equation functions at top level only, not inside let/where
Int literal in Float contextUse to_float n or write the literal with a dot: 5.0

§22 Package Manager

Synoema packages are source-only — only .sno files, verified at publish time. No native binaries, no .so or .dll files are allowed in the registry.

Quick start

sno pkg login           # authenticate via GitHub or Google
sno pkg add sno-http    # install a package
sno pkg search http     # search registry
import "sno-http"
use Http (http_serve, http_router)

main = http_serve 8080 my_router

Import layers

LayerExamplesRequires
Syntaxoperators, let, fn, traitnothing
Builtinserror, show, sin, tcp_listennothing
PreludeResult, Maybe, Map, Ok, Errnothing (auto-loaded)
Packageshttp_serve, http_router, http_jsonimport "sno-http" + sno pkg add sno-http

Only Layer 4 requires an explicit import statement and sno pkg add. Layers 1–3 are always available.

§23 Binary Metadata & Documentation Extraction

Compiled .wasm and .bc artifacts embed documentation metadata by default — types, contracts, and doc comments — accessible without the source file.

Controlling embedding

# Default: embed docs in output
sno build examples/quicksort.sno
sno wasm examples/factorial.sno

# Production / size-sensitive: skip embedding (~5–15% smaller)
sno build --no-docs examples/quicksort.sno
sno wasm  --no-docs examples/factorial.sno

Extracting from a compiled binary

sno doc-from-binary ./quicksort.wasm           # JSON (default)
sno doc-from-binary ./quicksort.wasm --format md  # Markdown
sno doc-from-binary ./quicksort.sno.bc         # from bytecode

JSON schema

{ "version": "0.1",
  "types":     [{ "name": "Shape", "kind": "adt", "signature": "Shape a",      "doc": "..." }],
  "contracts": [{ "function": "safe_div", "requires": "b != 0", "ensures": null }],
  "docs":      [{ "name": "map", "signature": "a -> b -> [a] -> [b]",          "doc": "..." }] }
FormatStorage location
.wasmWASM custom section .sno.metadata
.bc bytecodeText block [.sno.metadata] appended at end
Native ELF/Mach-OPlanned for v0.2

§21 CLI Commands

CommandDescription
sno run file.snoRun via interpreter (all features)
sno jit file.snoRun via Cranelift JIT (~3× faster than Python)
sno eval "expr"Evaluate expression, print result
sno check file.snoParse + typecheck without running
sno verify file.snoCheck + run with timeout, JSON output
sno test dir/Run doctests, unit, and property tests
sno fmt file.snoFormat file in-place
sno doc file.snoGenerate documentation from --- doc-comments
sno doc examples/Generate docs for all .sno files in a directory
sno doc-from-binary ./app.wasmExtract embedded documentation from a compiled binary
sno wasm file.snoCompile to WebAssembly (.wasm, embeds docs by default)
sno wasm --no-docs file.snoWASM without embedded documentation
sno build --native file.snoAOT native binary (macOS/Linux x86_64 or aarch64)
sno build --no-docs file.snoBytecode without embedded documentation
sno new myappCreate a new project
sno watch run file.snoRe-run on every file save
sno doctorCheck installation (PATH, GBNF, version)
sno setup claudeConfigure MCP integration for Claude
sno --errors json run file.snoErrors in JSON format (for LLM tools)

More on installation: Start · User Guide · IoT Platform