muro-lang.dev / docs
Identity
In brief: {a ≡ b : A} is a type. refl checks only when both sides convert. rewrite takes an explicit motive. Equations never execute.
The type
{plus (half n) (half n) ≡ n : Nat}
ASCII: {plus (half n) (half n) == n : Nat}.
The sides a, b and the sort A are checked in spec. An identity is not a program. You cannot emit it. You cannot + it.
Kernel identity on F32 and on Tensor F32 S is refused. Machine floats are not a setting for definitional equality. See Machine numbers.
refl
refl checks against {x ≡ y : A} only when x and y convert.
Conversion, in order:
- Syntactic equality. This does not spend fuel.
- The same definition applied to arguments: the arguments are compared. If they convert, the applications convert. If that comparison runs out of fuel, conversion stops.
- One unfolding, when the arguments do not convert and this definition is not already unfolding. A re-entry of that definition unfolds only when the match it reduces to fires on a constructor. Otherwise the application is neutral, and the argument mismatch stands. Two stuck calls that differ in an argument the unfolding does not erase fail here, at any fuel.
-
Otherwise weak head normal form, then compare again. A definition that matches on a later argument still converts:
g Nat (suc 0) kandg Nat 0 kdiffer in an argument, one unfolding matches on the second argument, and both sides reduce tok. A definition that ignores an argument converts the same way, because one unfolding makes the bodies identical.
Fuel is one budget for the argument comparison and the unfolding that follows it (@fuel 2000 in Elixir, mix muro.check --fuel N to change it). Infer and check recurse on the term; they do not spend that fuel. Running out is the error out of fuel, and it means the budget was spent. It is not a failed conversion.
So this evidence checks, after zeros is a productive unfold whose head is 0:
def head-zeros : evidence {head zeros ≡ 0 : Nat} :=
refl
(examples/zeros.muro.)
If the two sides do not convert, refl fails. You need rewrite, or a match that makes them convert in each branch.
rewrite
rewrite eq motive (λ z → P) in t
eq has type {lhs ≡ rhs : A}. The body t is checked as P with z replaced by rhs. The whole rewrite has type P with z replaced by lhs.
You write the motive. There is no tactic that finds it.
eq is checked in evidence, and its uses are discarded: in a run function the equation costs nothing at runtime and is not emitted. Inside a spec term (a type, an erased argument) eq is checked in spec, so a type may rewrite along an erased variable: Π (-e : {n ≡ m : Nat}) → Vec A (rewrite e motive (λ _ → Nat) in m) is well-formed. What is evidence may be used in spec, never the other way round.
From plus_suc in examples/half_ok.muro:
def plus_suc : evidence Π (n : Nat) → Π (m : Nat) →
{plus n suc(m) ≡ suc(plus n m) : Nat} :=
λ (n : Nat) → λ (m : Nat) →
match n motive (λ np → {plus np suc(m) ≡ suc(plus np m) : Nat})
| 0 => refl
| suc np =>
rewrite plus_suc np m motive (λ z → {suc(z) ≡ suc(suc(plus np m)) : Nat}) in
refl
The 0 branch converts immediately. The suc branch rewrites along the inductive hypothesis, then refl.
half_ok nests two rewrites: first plus_suc (half p) (half p), then half_ok p e2, then refl.
Evidence is an ordinary term
There is no separate proof language. A theorem is a λ-term that uses match, refl, rewrite, and matchEmpty. It pays affinity and descent. Instantiating it (half_ok p e2) does not consume the caller’s affine resources — those arguments are checked in evidence, and their uses are discarded — but the λ-binders you write inside the theorem still do.
Emit drops the whole definition. See Emit.
Next: Data.
Source on GitHub
· fetched from murolang/muro manual/