Learn

Learn U

From a function to a concurrent web server with database transactions. Every snippet is valid U — paste any into the playground to see the C it produces.

Functions

A function is f, a name, typed parameters, and a return type. The body is indented with tabs, and r => returns. Return type is optional for void functions.

U
f add(first: I, second: I) -> I
	r => first + second

f greet(name: S) -> S
	r => "Hello, {{name}}!"

f log_it(msg: S)
	System.stderr(msg)

Scalar types

I integer, N float, S string, L boolean, Q rational, D date, B byte. Sized variants: I8 I16 I32 I64 U8 U16 U32 U64 N32 N64.

U
f area(width: N, height: N) -> N
	r => width * height

f is_even(num: I) -> L
	r => num % 2 == 0

Lists and maps

[T] is a list, {K: V} is a map. Iteration uses .on() — no for loops. Pipelines chain, and the compiler fuses them into one pass.

U
f even_squares(nums: [I]) -> [I]
	r => nums.filter(num => num % 2 == 0).map(num => num * nn)

// Accumulator needs +M — locals are immutable by default
f total(prices: [N]) -> N
	sum: N +M = 0.0
	prices.on(price => (
		sum = sum + price
		none
	))
	r => sum

Classes

d declares a type. Fields are +M by default — the class's own methods (t) can mutate them. Mark -M for frozen-after-construction fields.

U
d Point
	x: N             // +M by default — instance field
	y: N
	id: I -M          // frozen after construction

	f move(dx: N, dy: N)
		t.x = t.x + dx   // t = self
		t.y = t.y + dy

	f distance() -> N
		r => Math.sqrt(t.x * t.x + t.y * t.y)

+R — where the value lives

Without +R, values live on the stack. +R moves them to the refcounted heap — the compiler emits retain/release. No garbage collector, no manual free.

U
d Session
	token: S
	user: S

f open(tok: S, user: S) -> Session +R
	r => Session({ token: tok, user: user })

±M — who can mutate what

The core safety rule: +M defaults to things owned by an instance. Everything else is -M. Parameters are -M — the callee borrows read-only. -M propagates inward through fields and elements.

U
// -M parameter: can't modify the Point through it
f describe(pt: Point) -> S
	// pt.x = 5.0  ← compile error: -M propagates
	r => "(" + pt.x.__string__() + ", " + pt.y.__string__() + ")"

// +M parameter: declares intent to modify
f zero_out(pt: Point +M)
	pt.x = 0.0
	pt.y = 0.0

// Element +M hole-punch: container frozen, elements writable
f normalize(pts: [Point +M])
	// pts.push(...)  ← compile error: container is -M
	[1..pts.len].on(idx => (
		pts[idx].x = pts[idx].x / 100.0  // ok — element is +M
		pts[idx].y = pts[idx].y / 100.0
		none
	))

x — errors with typed handlers

x ErrorType(...) throws a typed error. ! in the return type declares what can fail. x.on(handler) registers a catch handler — errors dispatch by type, not by position.

U
d DivideByZero : Error

f safe_div(num: N, den: N) -> N ! DivideByZero
	den == 0.0 ? x DivideByZero()
	r => num / den

f try_it() -> S
	x.on((err: DivideByZero) => (
		r => "cannot divide by zero"
	))
	result = safe_div(10.0, 0.0)
	r => result.__string__()

+G — static and global state

+G fields belong to the class, not the instance. They are -M by default — global constants. Mutable globals need +G +M and all writes must use <<.

U
d Config
	MAX_RETRIES: I +G = 3        // global constant — no annotation needed
	APP_NAME: S +G = "MyApp"     // global constant

d Counter
	val: I +G +M = 0             // global mutable — MVCC-managed

	f+G increment()              // +G = static method (no t)
		Counter << { val: Counter.val + 1 }

	// Counter.val = 5  ← compile error: use << for +G +M

MVCC — the << operator

<< is the atomic write — patches one or more fields in a single operation. No lock, no mutex, automatic retry on conflict.

U
d Metrics
	requests: I +G +M = 0
	errors: I +G +M = 0
	last_path: S +G +M = ""

f on_request(path: S)
	// Patch two of three fields atomically
	Metrics << { requests: Metrics.requests + 1, last_path: path }

f on_error(path: S)
	Metrics << { errors: Metrics.errors + 1, last_path: path }

// Instance MVCC — t can use << too
d Account +R
	balance: Q
	holder: S
	status: S

	f deposit(amount: Q)
		// Patch balance and status, leave holder unchanged
		t << { balance: t.balance + amount, status: "active" }

Transactions — << ( ... )

Multiple << patches, one atomic commit. Only pure, deterministic code inside. Retries automatically on conflict. x Rollback(...) exits cleanly.

U
d AccA
	bal: Q +G +M = 0.0
	name: S +G +M = ""
	last_tx: I +G +M = 0
d AccB
	bal: Q +G +M = 0.0
	name: S +G +M = ""
	last_tx: I +G +M = 0

f transfer(amount: Q, tx_id: I) ! InsufficientFunds
	rate = get_rate()             // +D — before the block
	<< (
		fee = compute_fee(AccA.bal, rate)    // -E -D only ✓
		AccA.bal < amount + fee ? x Rollback("insufficient")
		AccA << { bal: AccA.bal - amount - fee, last_tx: tx_id }
		AccB << { bal: AccB.bal + amount, last_tx: tx_id }
		// name is NOT patched — stays unchanged
	)                             // all-or-nothing
	log("transferred")            // +E — after the block

+A — async fibers

a before a call makes it async — a fiber. The scheduler handles concurrency on one thread with epoll. No ThreadPoolExecutor, no GIL.

U
a f fetch(url: S) -> S
	resp = a HTTP.get(url)
	r => resp.text()

// Fan-out: fetch all URLs concurrently
f fetch_all(urls: [S]) -> [S]
	tasks = urls.map(url => a fetch(url))
	r => tasks

Events — e(obj).on()

e(obj) wraps an object as an event emitter. .on() subscribes. owner defaults to t inside methods — cleanup is automatic.

U
d ClickEvent
	x: I -M
	y: I -M

d Button +R
	label: S
	f click(x: I, y: I)
		e(t).emit(ClickEvent({ x: x, y: y }))

d Page +R(RAII)
	f setup(btn: Button +R)
		// owner defaults to t — auto-removed on Page cleanup
		e(btn).on(click => Log.info("clicked"))

		// need the source? second param is EventContext
		e(btn).on((click, ctx) => (
			Log.info(ctx.target.label + " at " + click.x.__string__())
		))

HTTP server

The handler is a pure function: Request in, Response out. The runtime handles TCP, epoll, fibers, keep-alive.

U
f list_users(req: Request) -> Response
	users = Database.Query({ store: "users" })
		.select(["name", "email"])
		.orderBy("name", "ASC")
		.limit(50)
		.fetchAll()
	r => Response.json(users)

f main()
	serve(8080, {
		"GET /users": list_users,
		"POST /users": create_user
	})

Database queries

The query builder produces parameterized SQL — dialect-aware ($1 for Postgres, ? for MySQL). No injection, ever.

U
d User : Database.Row
	name: S
	email: S
	score: I

f top_users(min_score: I) -> [User]
	r => Database.Query({ store: "users" })
		.where("score", ">=", min_score)
		.orderBy("score", "DESC")
		.limit(10)
		.fetchAll()

// Transactions work with database rows too
f award(from_id: I, to_id: I, points: I) ! NotEnough
	<< (
		sender = Database.Query({ store: "users" }).where("id", "=", from_id).fetchRow()
		recipient = Database.Query({ store: "users" }).where("id", "=", to_id).fetchRow()
		sender.score < points ? x Rollback("not enough")
		sender << { score: sender.score - points }
		recipient << { score: recipient.score + points }
	)

Templates

Template literals with compile-time validation. SQL templates lower to prepared statements automatically.

U
// HTML template — injection-safe
f page(title: S, body: S) -> S
	r => HTML`<html><head><title>{{title}}</title></head>
		<body>{{body}}</body></html>`

// SQL template — compiles to prepared statement
f find_user(email: S) -> Tree
	r => SQL`SELECT * FROM users WHERE email = {{email}}`

±E ±D — effects and determinism

Two orthogonal axes that track what a function does: +E = has side effects (I/O, logging, network). +D = nondeterministic (random, clock, external input). The defaults are -E -D — pure and deterministic. This is what makes << ( ) transactions safe: the compiler only allows -E -D code inside.

U
// Pure, deterministic — safe for transactions, memoization, vectorization
f compute_fee(balance: Q, rate: Q) -> Q
	r => balance * rate * 0.01

// Effectful — writes to the outside world
f+E save_log(msg: S)
	File.append("/var/log/app.log", msg)

// Nondeterministic — reads from unpredictable source
f+D roll_dice() -> I
	r => Random.int(1, 6)

// Both — effectful AND nondeterministic
f+E+D record_timestamp()
	ts = Time.now()                  // +D: reads clock
	File.append("log.txt", ts.__string__())  // +E: writes file

// This is why transactions work:
<< (
	fee = compute_fee(bal, rate)    // -E -D ✓ — allowed
	// save_log("hi")              // +E — compile error!
	// dice = roll_dice()          // +D — compile error!
	Account << { balance: bal - fee }
)

Generators and +W streams

e value yields a value from a generator — the function suspends and resumes when the consumer pulls the next value. +W marks the return type as an infinite stream. [1..w] is a built-in infinite range.

U
// Fibonacci generator — infinite stream
f fib() -> I +W
	prev = 1
	curr = 1
	[1..w].on(idx => (
		e curr                    // yield current value
		next = prev + curr
		prev = curr
		curr = next
		none
	))

// Consume: take first 10 Fibonacci numbers
f first_ten() -> [I]
	r => fib().take(10)

// Pipelines work on infinite streams
f even_fibs() -> [I]
	r => fib().filter(num => num % 2 == 0).take(5)

z f — compile-time evaluation

z f declares a function that runs at compile time. The compiler evaluates it, inlines the result, and emits no runtime call. Must be -E -D (pure, deterministic) — the compiler can't do I/O.

U
// Computed at compile time — no runtime cost
z f factorial(num: I) -> I
	num <= 1 ? r => 1
	r => num * factorial(num - 1)

// The compiler evaluates this and emits: result = 120
f main() -> I
	result = factorial(5)
	r => result

// z f __load__() runs at module load time
z f __load__()
	Config << { root: load_config_tree() }
	r => none

c — explicit copies

c expr produces a deep copy. The compiler doesn't copy implicitly — when you want a snapshot of mutable data, you say so.

U
d State +R
	data: [I]

f snapshot(state: State +R) -> State +R
	r => c state             // deep copy — independent of original

// c+R promotes a stack value to the heap
f make_heap(val: I) -> [I] +R
	local_list = [val, val * 2, val * 3]
	r => c+R local_list      // copy to heap, return +R

+V — vector lanes

+V on data means vector-ready layout. On a function, it means vectorizable computation. The emitted C uses SIMD intrinsics — SSE/AVX on x86, NEON on ARM.

U
f scale_all(arr: [I +V] +R) -> [I +V] +R
	r => arr.map(val => val * 3)

f dot_product(values: [N], weights: [N]) -> N
	sum: N +M = 0.0
	[1..values.len].on(idx => (
		sum = sum + values[idx] * weights[idx]
		none
	))
	r => sum
	// -M default on sum forces local accumulator
	// → GCC/Clang auto-vectorizes into SIMD

+R(GPU) — device memory

+R(GPU) places data in device memory. A .map() over it becomes a WGSL compute shader.

U
f brighten(pixels: [I] +R(GPU)) -> [I] +R(GPU)
	r => pixels.map(px => px * 3 + 1)

Where to go next

The specification is the complete reference. The interview page compares U to Python on five real problems. The snippets page has copy-paste examples. The feature status tells you honestly which of it runs today.