muro-lang.dev / docs
Terms
In brief: one sort Type. Binders are written. Application is juxtaposition. match takes an explicit motive. Affine is the default.
The book
A .muro file is a book: a sequence of ν, data, and def entries.
def name : tag type := body
The tag is run, run internal, spec, or evidence. There is no other tag. These words are rejected as tags (they remain ordinary identifiers): live, dead, proof, proof evidence, ghost, comp, export.
Forward references are allowed. The checker sees every definition when it checks any one of them.
Type
There is one sort, Type. It is not Type : Type. Type itself is erased: you do not compute with it.
Primitive types you can write as atoms:
| Atom | Meaning |
|---|---|
Type |
The sort |
Nat |
Peano naturals |
Unit |
One constructor, tt |
Empty |
No constructors |
I64 / F32 |
Machine scalars; see Machine numbers |
Stream A |
Greatest fixed point; see Streams |
Nat, Unit, and Empty as types are spec formers. You infer them only in spec. Their constructors (0, suc, tt) compute in run.
Binders and quantities
A binder is always parenthesized.
(n : Nat) affine (default): at most one run/evidence use
(+ n : Nat) reuse: only if the type WHNFs to Data
(- e : IsEven n) erased: compile-time; cannot be used computationally
The + or - sits immediately before the name, inside the parentheses. (-A : Type) and (- A : Type) both parse.
Data (after WHNF) for +: Nat, Unit, Empty, I64, F32, Tensor, or a user data type whose parameters are Data. Indices do not have to be Data. List A is Data iff A is. List (Nat → Nat) is not, so you cannot write +xs : List (Nat → Nat).
Stream and Either are not Data. There is no + on a stream or on a refutation P → Empty.
In spec, uses are forgotten. You can mention an affine variable twice while building a type.
In run and evidence, using an affine variable twice is an error: affine variable used twice. Using an erased variable computationally is an error: erased variable used computationally.
Π and λ
Π (n : Nat) → Nat
λ (n : Nat) → suc(n)
ASCII: Pi, lam, ->.
A non-dependent arrow A → B is Π (_ : A) → B with an affine ignored binder.
Application is juxtaposition: f a b. motive, in, and def never start an argument.
match
Every eliminator writes its motive. The motive is the family you return in, with the scrutinee bound.
Nat:
match n motive (λ x → P)
| 0 => tz
| suc p => ts
x is bound in P. p is bound in ts. suc p here is a pattern binder, not suc applied to a term.
Empty:
matchEmpty e motive (λ _ → P)
There are no constructors. If you have an inhabitant of Empty, you may return any P.
Data (one named branch per constructor):
match m motive (λ _ → A)
| nothing => d
| just a => a
Motives are written in parentheses. Nested λ in the motive cover index binders (see Indexed data).
Products
A × B (ASCII *) is a pair type. (a, b) is a pair. fst and snd project. head s is fst (uncons s); tail s is snd (uncons s).
Recursion and descent
A self-call in run or evidence must descend on a non-erased argument: a variable marked smaller because it came from a match (the suc predecessor, a constructor argument whose type is D …, the tail of a list, …).
Spec does not check descent. IsEven may recurse on p after two suc matches because it is a spec.
A typical run recursion looks like plus:
def plus : run Π (n : Nat) → Π (m : Nat) → Nat :=
λ (n : Nat) → λ (m : Nat) →
match n motive (λ _ → Nat)
| 0 => m
| suc np => suc(plus np m)
np is smaller than n. plus np m is allowed. plus n m inside the suc branch is not.
The half example, as a reader
examples/half_ok.muro puts the three modes in one file.
-
plus— run. Adds Peano numbers. -
IsEven— spec. A familyNat → Type.0is even (Unit).1is odd (Empty).suc(suc p)is even iffpis. -
half— run. Drops twosucat a time. -
plus_suc— evidence.{plus n suc(m) ≡ suc(plus n m) : Nat}. -
half_ok— evidence. Ifnis even,{plus (half n) (half n) ≡ n : Nat}.
half of eight is four. The proof is match, refl, rewrite, and matchEmpty. It is not emitted.
Next: Identity.
Source on GitHub
· fetched from murolang/muro manual/