symbolic core
Types, values, canonical form, content addressing and word aliases. Pinned golden vectors make canonical drift impossible to do by accident.
// NODE 0x02 — TYPE IDENTITY
An H3 for types. Every type gets a canonical form, a content address computed from its structure alone, and three ordinary words you can say out loud.
>
Identity from structure, not from a name anyone chose.
H3 gives every hexagon on Earth a canonical ID that anyone can compute without asking anyone. unitype does it for types. Normalization gives each semantic type exactly one serialization, and a blake3 hash over those bytes is its identity — excluding names, prose and annotations, because none of those are what makes a type what it is.
So two systems that have never exchanged a schema can find out they hold the same type by comparing 32 bytes, and two people can find out by comparing a phrase.
$ unitype --project examples/demo ls types
demo::Circle struct wet-either-model
demo::Rect struct code-twice-wheat
demo::Shape struct escape-until-deposit
geo::Point struct proof-mystery-trouble
geo::Place struct zoo-page-aware
gfx::Marker struct run-spawn-replace
gfx::Vec2 struct proof-mystery-trouble
geo::Point and gfx::Vec2 get the same three words because they hold the same data — which nobody told it. Types live in universes, and between universes nothing is compatible by accident: data crosses only through an explicit link that carries a witness anyone can read.
Which implementation runs is a policy decision, not an architectural one.
A function signature is a proposition. An implementation of it is a proof. There can be many proofs of one proposition — compiled or interpreted, native or sandboxed — each declaring its own time and space cost. Both are checked against the signature at the boundary, so a proof that lies about what it proves is caught rather than trusted.
$ unitype run demo::sort \
--args '[[5,3,9,1]]' --policy fastest -v
candidates:
sort.rust_merge here O(n log n) / O(n)
sort.sandboxed sandbox O(n log n) / O(n)
sort.py_bubble python O(n^2) / O(1)
chose sort.rust_merge in here
[1,3,5,9]
$ unitype run demo::sort \
--args '[[5,3,9,1]]' --policy min-memory -v
candidates:
sort.py_bubble python O(n^2) / O(1)
sort.rust_merge here O(n log n) / O(n)
sort.sandboxed sandbox O(n log n) / O(n)
chose sort.py_bubble in python
[1,3,5,9]
Same proposition, same answer. One ran in-process in Rust; the other ran in Python over a pipe.
$ unitype conform
demo::sort 3 proofs, 7 cases — all agree
not checked here: sort.sandboxed
demo::reverse<T> 2 proofs, 5 trials — free theorem holds
A generic proposition is a claim about every type. A proof of reverse<T> cannot look at a T, so every T it returns must be one it was given — and conform checks that from the signature alone, with nobody writing down an expected answer. A proof that sorts when it sees integers is caught.
The loop closes: a type can be described, checked, addressed, named, bridged, generated and computed — in more than one language. 735 tests, including the subtyping, inference and shape laws as property tests.
Types, values, canonical form, content addressing and word aliases. Pinned golden vectors make canonical drift impossible to do by accident.
Inheritance, subtyping, refinement, generic bounds, and variance inferred from where each parameter occurs. Structural addressing erases names across a whole workspace.
Derived from two declarations, composed along a path, and run through the expression language. An equivalence loses nothing, so one link runs both ways.
Total by construction, yet able to fold, case, match and compare. demo::sorted checks a list is in order in all three target languages without anyone implementing it.
Environments register what they can prove; a policy decides which proof answers a call; a worker keeps an interpreter alive between calls.
A proof compiled to WebAssembly gets memory, a budget and nothing else — no files, no sockets, no clock. Somewhere to run a proof you did not write.
TypeScript, Python and Rust, checked by running the generated code in all three and comparing it against what unitype says the value is.
The proofs of one proposition checked against each other, plus free theorems for the generic ones. Exit code 4 when they disagree.
A built-in universe describes the representation, and is checked against it, so a declaration is a value that validates, addresses and travels like any other.
Rust 1.85 or newer.
$ cargo install unitype-cli
$ git clone https://github.com/CarterCole/unitype
$ cd unitype
# three universes, eleven types, nine proofs in three environments
$ unitype --project examples/demo check
# what every platform calls an HTTP request
$ unitype --project examples/http words http::request
http::request(req: http::Request) -> future<http::Response>
address ut1:cd212e2df458227e6cc86b268247421129122977094fe54b560f7a24a0d3a9f6
words essay-border-siren
Called:
python requests.request in python
rust reqwest::Client::execute in rust
sh curl in shell
typescript fetch in browser
typescript fetch in node
curl is a program, fetch is a global, requests is a package you install — three different kinds of thing. Underneath they answer the same proposition, and essay-border-siren is what that proposition is called when you have to say it to a person.
The specification is normative, so this is implementable in any language. The Rust workspace is the reference, not the definition.
User in different universes are unrelated until someone links them; two identically-shaped types with different names have the same address.ut1: addresses remain the authority.