muro-lang.dev / docs
Extending the kernel
In brief: this is Job B. If you only want a program, stop and go back to Start. Order is mandatory. If Agda and Elixir disagree, Agda wins.
Order
-
Agda syntax (
agda/Muro/Syntax.agda) if you add a constructor — bothTm nandPTm V. -
Subst (
agda/Muro/Subst.agda):wk,sub,toPHOAS,unembed. Pattern-lambdas passed tosubdo not compute; use a named function (instσ,motSucσ). -
Check (
agda/Muro/Check.agda): adecideclause. Mixfix inMuro.Judgementisσ , Γ ⊢[ m ] e ⇒ A ⊣ u. -
make agdauntil the example twin decides (half_ok-checksisreflfor the canonical book). Twins are examples of the rules, not a proof that the book is correct. -
Elixir mirror, same shapes, each checker clause commented with the Agda constructor (
⇒-var-run,⇐-refl, …):-
lib/muro/ast.ex -
lib/muro/subst.ex -
lib/muro/check.ex -
lib/muro/parser.ex/lib/muro/emit.exif it is surface or run code
-
-
mix testandmix muro.check.
Do not add a fourth representation. Do not use raw HOAS (Tm → Tm) as inductive syntax.
toPHOAS / unembed live in agda/Muro/Subst.agda. Elixir Muro.Ast.to_db/2 sends named FOAS to de Bruijn (unbound names become {:def, name}).
Elixir constraints
- ASCII identifiers only. No unicode primes, no mixed-script atoms.
-
Do not define local
hd/1. -
Guards cannot call ordinary
defphelpers.
What Agda is
Agda holds the rules. It is not a certificate that a .muro file is correct. It is not what mix muro.check runs.
-
Muro.Check— fuelled decision procedure. -
Muro.Judgement— inductive ⊢ for the core fragment. -
Muro.Wall— mode wall lemmas. No promotion. -
Muro.Consistency— closed evidence of Empty, for Core terms (no application or rewrite). The full statement is not proved.Empty-evidis not proved.
Example twins (Example*.agda) are examples of those rules.
Build
Agda 2.8+ and standard library 2.3 (no --type-in-type):
git clone --depth 1 --branch v2.3 https://github.com/agda/agda-stdlib.git vendor/agda-stdlib
make agda
make agda is agda --no-libraries -i agda -i vendor/agda-stdlib/src. The stdlib checkout ships extra .agda-lib files that must not be loaded.
CI runs the Elixir job and make agda on every push and pull request.
Named FOAS (parser output)
{:var, name}
:typ | :nat | :ze | {:su, t} | :unit | :one | :empty
{:pi, qty, a, name, b}
{:lam, qty, a, name, t}
{:app, f, a}
{:mnat, e, x, p, z, y, s}
{:memp, e, x, p}
{:munit, e, x, p, u} -- kernel only
{:idt, ty, a, b}
:rfl
{:rwt, eq, x, p, t}
{:def, name}
{:ann, e, a} -- kernel only
{:prod, a, b} | {:pair, a, b} | {:fst, t} | {:snd, t}
{:stream, a} | {:unf, seed, f} | {:ucons, s}
{:sum, a, b} | {:left, t} | {:right, t}
{:msum, e, x, p, a, l, b, r}
qty is :affine | :reuse | :erased. A book entry:
%{name: "half", mode: :run, export: true, type: named, body: named}
%{name: "IsEven", mode: :spec, type: named, body: named}
%{name: "half_ok", mode: :evidence, type: named, body: named}
export is present only on :run (true → def, false → defp).
De Bruijn drops the name strings: {:pi, q, a, b}, {:lam, q, a, t}, {:mnat, e, p, z, s} with index 0 = nearest binder.
Bidirectional rules
σ , Γ ⊢[ m ] e ⇒ A infer
σ , Γ ⊢[ m ] e ⇐ A check
Hard rules you must not relax without a new theory:
-
A run or evidence variable is used at most once, unless
+on Data. - Run and evidence recursion must descend on a non-erased argument. Spec does not check descent.
- No promotion. Erased variables have no computational use.
- Emitted code is run only.
Conversion: syntactic equality first; then stuck-def congruence when the first argument is not constructor-headed; then WHNF. Fuel is for conversion only.
After you change the kernel, update this manual so manual/ stays the language book. Do not put the change only in @moduledoc.
Source on GitHub
· fetched from murolang/muro manual/