Real numeric types
BigInt, rationals, fixed-point, and floats — typed numeric literals across widths, not just 64-bit integers. Financial math without the workarounds.
The language
Every developer has written this query. It has survived five decades in every language that touches data: SQL, LINQ, XQuery, Python and Haskell comprehensions — one shape, everywhere. That persistence is not a syntax fashion. The comprehension is what data access looks like when you say honestly what you are doing: naming a source, filtering it, projecting the survivors.
But a query can only ask. It hands its result to someone else — an application, in another language — to act on. LINQ existed precisely to close that gap for reads. f1r3lang goes the rest of the way: the whole language is built around the comprehension, and the comprehension gets a body. The DO is a process, not a value. It runs. It can send, spawn further comprehensions, or do nothing — there is no host language to hand results back to, because there is no outside.
SQL SELECT spec FROM prey WHERE chi(spec) LINQ from spec in prey where Chi(spec) select spec XQuery for $spec in $prey where chi($spec) return $spec Python [spec for spec in prey if chi(spec)] Haskell [ spec | spec <- prey, chi spec ] // f1r3lang — the same shape, plus a DO for( @spec <- prey where Chi(spec) ) { Break!(spec) }
A query language has one privileged verb. f1r3lang has two, and the second is why the first behaves differently than it does in SQL. The write is a term. It composes in parallel with everything else using |, and it is addressed the same way the read is: the reader names prey, the writer names prey, neither names the other.
In SQL, INSERT and SELECT are different kinds of statement, mediated by an engine that owns the table. Here they are two terms in one program — and the table is not owned by anybody.
// the write — a term like any other prey!( spec ) // reader and writer, composed in parallel prey!( spec ) | for( @s <- prey where Chi(s) ) { Break!(s) }
For readers coming from SQL
f1r3lang distinguishes three ways of reading, and the distinction is exactly the one a database developer already makes between a destructive read, a standing query, and a plain look. Choosing among the three arrows is the main design decision in a f1r3lang program.
| f1r3lang | SQL reading |
|---|---|
x!(Q) | INSERT — put a row on x |
for( p <- x )P | SELECT … FOR UPDATE, then DELETE, then act. Consumed by exactly one reader. |
for( p <= x )P | A standing query, or a trigger: the reader persists and fires again on the next matching write. |
for( p <<- x )P | A peek: plain SELECT. Reads without consuming, so the datum remains for others. |
where cond | WHERE |
& | A join — and simultaneously a distributed atomic commit across the joined channels. |
; | Sequence: this group of receipts, then that one. |
| the pattern | The projection — structural rather than columnar, matched up to the equations of the theory that types the channel. |
One of those rows deserves a second look. A SQL join is over static extents: the rows are already there. A f1r3lang join is over concurrent writers — the rows are being produced by other running programs, and the join is a rendezvous. It succeeds when a matching datum is available on every joined channel at once, and until then it waits. The protocol machinery that would normally implement this — prepare, vote, commit, coordinator, timeout — is not written, because the rule that fires is the multi-channel one.
When a reader and a writer meet on a channel, one rule fires, and the substitution it performs is the witness that the meeting occurred. After the step, the running term contains the evidence: the datum is sitting in the continuation, in the position the pattern named. Nothing external recorded the event. The event left a mark on the program.
A communication is atomic, and its atomicity requires no support from the runtime. The condition is evaluated before the step commits; if it fails, nothing is consumed, so there is no partial state and nothing to compensate. A failed transaction is not rolled back — it never began. The message stays in the tuple space, exactly as a non-matching pattern would leave it.
Because there is an identified event, there is something to charge for: the unit of billing is the unit of commitment. That is what makes metering honest.
// only commit when the pattern matches // AND the guard is true. a failing guard // leaves the message in the tuple space. new chan, stdout(`rho:io:stdout`) in { chan!(-3) | chan!(0) | chan!(7) | for (@x <- chan where x > 0) { stdout!(("kept", x)) } }
Underneath the comprehension there are no variables, no objects, no shared memory. A f1r3lang program is made of exactly two kinds of values: processes — everything that computes, including literals like 42 — and names — the channels processes communicate on.
The two convert into each other: @P quotes a process into a name; *x dereferences a name back into a process. That reflection is the "rho" in rho‑calculus — reflective higher order — and it is what lets the language treat code as data without leaving the formal model.
Concurrency is not a feature bolted on: the | operator is how programs are built, channels hold messages as a bag rather than a queue, and sends never block. Sequence is what you construct deliberately, when you actually need it.
new chan in { chan!(42) | // send 42 on chan for (@value <- chan) { // receive it stdout!(value) // value is 42 } } // sequence, when you mean it: new ack in { chan!("first", *ack) | for (_ <- ack) { chan!("second") } }
Names created with new are cryptographically unique. No process outside the block that created one can guess it, reconstruct it, or convert bits back into it — the language has no construct for forging a name, even when its bits are visible on chain.
That's capability-based security as grammar. Access to a channel is the permission to use it. The contract-draining bugs of shared-state languages — reentrancy, confused-deputy calls — depend on reaching state you shouldn't; here that state is a name you were never given.
new privateChan in { // only this block holds privateChan privateChan!("secret") | for (@msg <- privateChan) { stdout!(msg) } }
Under the hood
Programs run on RSpace, a pattern-matching tuple space that replaces the key-value store. Execution is reduction: the runtime matches sends against receives, concurrently, without locks. Every operation is metered in phlogiston — deploys carry a limit, unused gas is refunded. Non-deterministic scheduling, deterministic outcomes: whatever order reductions happen in, every validator converges on the same state, finalized mathematically by CBC Casper consensus.
On the research branch, metering goes a step further: cost lowers to sends and receipts in the language itself. Spending fuel is a communication, a budget is a stack of tokens, and running out of budget is a non-event rather than an error path — an unfunded operation simply never happens, which is exactly the same guarantee a failed guard gives. More in Research.
Beyond Rholang
BigInt, rationals, fixed-point, and floats — typed numeric literals across widths, not just 64-bit integers. Financial math without the workarounds.
Hierarchical trie structures with literal syntax and cursor-based navigation. Filesystem-shaped data as a first-class value.
A native test framework for contracts, used by the platform's own system contracts. Test on-chain logic like you test everything else.
.rhox macro files parameterize contracts at deploy time — one source, many configurations. The Proof-of-Stake contract itself ships this way.
rho:ai:* and rho:ollama:* — hosted and local models as system channels, with deterministic replay for consensus.
The node and interpreter were rebuilt in Rust — no JVM. Faster, leaner, and the recommended production implementation.