An Agda formalisation framework for second-order languages.
Accompanies the POPL 2022 paper Formal Metatheory of Second-Order Abstract Syntax.
Run
python soas.py <syntax> [-o <output>]
with <syntax>
as a path to a syntax file (examples given in gen/ex
, e.g. python soas.py gen/ex/lambda/stlc
). This assumes python
is Python 3. By default the generated Agda modules will be saved in the out
directory, but this can be customised with the -o
argument.
SOAS is a library for generating the formalised metatheory of second-order syntaxes, i.e. languages that involve terms with variable binding. Examples are the abstraction terms of computational calculi like the Ξ»-calculus, quantifiers of first-order logic, differentiation and integration operators, etc. Formalising such systems in a proof assistant usually involves a lot of boilerplate, generally associated with the representation of variable binding and capture-avoiding substitution, the latter of which plays a role in the equational theory or computational behaviour of second-order languages (e.g. instantiation or Ξ²-reduction).
Starting from the description of the second-order signature and equational presentation of a syntax, such as the following one for the simply typed Ξ»-calculus:
syntax STLC | Ξ
type
N : 0-ary
_β£_ : 2-ary | r30
term
app : Ξ± β£ Ξ² Ξ± -> Ξ² | _$_ l20
lam : Ξ±.Ξ² -> Ξ± β£ Ξ² | Ζ_ r10
theory
(ΖΞ²) b : Ξ±.Ξ² a : Ξ± |> app (lam(x.b[x]), a) = b[a]
(ΖΞ·) f : Ξ± β£ Ξ² |> lam (x. app(f, x)) = f
the library generates Agda code for:
- a grammar of types and an intrinsically-typed data type of terms;
- operations of weakening and substitution together with their correctness properties;
- a record that, when instantiated with a mathematical model, induces a semantic interpretation of the syntax in the model that preserves substitution;
- a term constructor for parametrised metavariables and the operation of metasubstitution;
- and a library for second-order equational/rewriting reasoning based on the axioms.
The high-level directory structure of the project is shown below:
.
βββ Everything.agda | All the Agda modules that make up the library
βββ SOAS | Main source directory
βββ gen | Code generation script directory
βΒ Β βββ ex | Example syntax files
βΒ Β βββ templates | Template files used in generation
βΒ Β βββ term.py | Term parsing and output
βΒ Β βββ type.py | Type parsing and output
βΒ Β βββ eq.py | Equality parsing and output
βΒ Β βββ util.py | Utilities
βββ out | Agda modules generated from the examples
βββ soas.py | Main entry script for the code generator
A browsable version of the library source code can be found here, and the example generated Agda modules here.
The SOAS framework includes a Python script soas.py
that compiles a simple and flexible syntax description language into Agda code. The language includes several features to make the construction of first- and second-order syntaxes as seamless as possible, such as (meta)syntactic shorthands, a module system, and a library of common algebraic properties. We start with the description of a basic syntax file for the simply-typed Ξ»-calculus, and introduce various extensions below.
A syntax description file consists of four components: the syntax header, the type declaration, the term operators, and the equational theory. The latter three are given under indented blocks preceded by the keywords type
, term
and theory
. Lines beginning with --
are parsed as comments and are therefore ignored.
syntax STLC | Ξ
STLC
is the root name of the generated Agda modules STLC.Signature
, STLC.Syntax
, STLC.Equality
. This would be used as the name of the data type of terms and type signature, but we override it with the optional annotation β¦ | Ξ
so the Agda type of terms is Ξ
and Agda type of sorts/types is ΞT
.
type
N : 0-ary
_β£_ : 2-ary | r30
Types must be given in an indented list of operators and arities, with optional attributes. We specify a nullary type constructor N
, and a binary type constructor _β£_
. The annotation β¦ | r30
declares the associativity and precedence of the binary infix operator, and gets turned into the Agda fixity declaration infixr 30 _β£_
.
term
app : Ξ± β£ Ξ² Ξ± -> Ξ² | _$_ l20
lam : Ξ±.Ξ² -> Ξ± β£ Ξ² | Ζ_ r10
Terms are given by a list of textual operator names and their type declarations, with optional annotations for symbolic notation and fixity. The type of an operator consists of a return sort given after a ->
, and a sequence of argument sorts separated by at least two spaces. The arguments may optionally bind any number of variables, written as (Ξ±β,β¦,Ξ±β).Ξ²
or just Ξ±.Ξ²
if there is only one bound variable (like in the case of lam
above). The textual operator names are used in the equational theory, for the operator symbols for the Agda term signature, and the constructors of the inductive family of terms. The latter may be overridden to a different symbol (that can be Unicode and mixfix, with a given fixity) with an annotation like β¦ | _$_ l20
or β¦ | Ζ_ r10
.
theory
(ΖΞ²) b : Ξ±.Ξ² a : Ξ± |> app (lam(x.b[x]), a) = b[a]
(ΖΞ·) f : Ξ± β£ Ξ² |> lam (x. app(f, x)) = f
The equational axioms of a syntax are given as a list of pairs of terms in
metavariable and object variable contexts. The general form of an axiom is (AN) π |> Ξ |- s = t
, where AN
is the name of the axiom, π = mβ : Ξ β.Οβ
Β β¦Β mβ : Ξ β.Οβ
is a double spaceβseparated list of parametrised
metavariables, Ξ = xβ : Ξ±β Β β¦Β xβ : Ξ±β
is a double
spaceβseparated list of object variables, and s
and t
are terms consisting
of metavariables, object variables, and operator applications. If
Ξ
is empty (as it often will be), the β¦ Ξ |- β¦
part of the declaration can
be omitted; similarly, (meta)variables of the same sort can be grouped as a b c : Ο
, and if : Ο
is not given, the variable type will default to the sort *
,
denoting un(i)sortedness. The operator applications in the terms are of the form
op (tβ, β¦, tβ)
, where the arguments may bind variables using .
: for example, a binary
operator op
that binds two variables in the first argument and none in the
second is written as op (a b.t[a,b], s)
. The terms associated with a metavariable environment are given between square brackets which may be omitted if the metavariable has no parameters. Given all this, the axiom ΖΞ²
is read as: given an arbitrary term b
of type Ξ²
with a free variable of type Ξ±
, and a term a
of type Ξ±
, the application of the abstraction lam(x.b[x])
to a
is equal to b
with all occurrences of the free variable replaced by a
. Every application of Ξ²-equivalence is an instance of this axiom, with b
and a
instantiated with concrete terms of the right type and context.
The script generates three Agda files (two if an equational theory is not given).
The signature file contains:
-
the (first-order) type declaration for the syntax, if it exists:
data ΞT : Set where N : ΞT _β£_ : ΞT β ΞT β ΞT infixr 30 _β£_
-
the list of operator symbols parametrised by type variables:
data Ξβ : Set where appβ : {Ξ± Ξ² : ΞT} β Ξβ lamβ : {Ξ± Ξ² : ΞT} β Ξβ
-
the term signature that maps operator symbols to arities:
Ξ:Sig : Signature Ξβ Ξ:Sig = sig Ξ» { (appβ {Ξ±}{Ξ²}) β (β’β Ξ± β£ Ξ²) , (β’β Ξ±) βΌβ Ξ² ; (lamβ {Ξ±}{Ξ²}) β (Ξ± β’β Ξ²) βΌβ Ξ± β£ Ξ² }
An arity consists of a list of second-order arguments and a return type. An argument may bind any number of variables. For example, the application operator has two arguments of sort
Ξ± β£ Ξ²
andΞ±
, binding no variables and returning aΞ²
. The lambda abstraction operator has one argument of sortΞ²
that binds a variable of sortΞ±
, and returns a function term of sortΞ± β£ Ξ²
. This second-order binding signature is sufficient to fully specify the syntactic structure of the calculus.
The syntax file contains:
-
the intrinsically-typed inductive family of terms with variables, metavariables (from a family
π
) and symbolic operators:data Ξ : Familyβ where var : β βΎΜ£ Ξ mvar : π Ξ± Ξ β Sub Ξ Ξ Ξ β Ξ Ξ± Ξ _$_ : Ξ (Ξ± β£ Ξ²) Ξ β Ξ Ξ± Ξ β Ξ Ξ² Ξ Ζ_ : Ξ Ξ² (Ξ± β Ξ) β Ξ (Ξ± β£ Ξ²) Ξ infixl 20 _$_ infixr 10 Ζ_
-
an instance of the algebra of the signature endofunctor, mapping operator symbols to terms of the syntax:
Ξα΅ : SynAlg Ξ Ξα΅ = record { πππ = Ξ» where (appβ β a , b) β _$_ a b (lamβ β a) β Ζ_ a ; π£ππ = var ; ππ£ππ = Ξ» πͺ mΞ΅ β mvar πͺ (tabulate mΞ΅) }
-
semantic interpretation of terms in any signature algebra
(π, π£ππ, ππ£ππ, πππ)
:π€ππ : Ξ βΎΜ£ π π : Sub Ξ Ξ Ξ β Ξ ~[ π ]β Ξ π (t β Ο) new = π€ππ t π (t β Ο) (old v) = π Ο v π€ππ (mvar πͺ mΞ΅) = ππ£ππ πͺ (π mΞ΅) π€ππ (var v) = π£ππ v π€ππ (_$_ a b) = πππ (appβ β π€ππ a , π€ππ b) π€ππ (Ζ_ a) = πππ (lamβ β π€ππ a)
-
proof that the interpretation is a signature algebra homomorphism:
π€ππα΅β : SynAlgβ Ξα΅ πα΅ π€ππ π€ππα΅β = record { β¨πππβ© = Ξ»{ {t = t} β β¨πππβ© t } ; β¨π£ππβ© = refl ; β¨ππ£ππβ© = Ξ»{ {πͺ = πͺ}{mΞ΅} β cong (ππ£ππ πͺ) (dext (π-tab mΞ΅)) } } where open β‘-Reasoning β¨πππβ© : (t : β Ξ Ξ± Ξ) β π€ππ (Ξα΅.πππ t) β‘ πππ (β β π€ππ t) β¨πππβ© (appβ β _) = refl β¨πππβ© (lamβ β _) = refl π-tab : (mΞ΅ : Ξ ~[ Ξ ]β Ξ)(v : β Ξ± Ξ ) β π (tabulate mΞ΅) v β‘ π€ππ (mΞ΅ v) π-tab mΞ΅ new = refl π-tab mΞ΅ (old v) = π-tab (mΞ΅ β old) v
-
proof that the interpretation is unique, i.e. it is equal to any signature algebraβhomomorphism
g
:π€ππ! : (t : Ξ Ξ± Ξ) β π€ππ t β‘ g t π-ix : (mΞ΅ : Sub Ξ Ξ Ξ)(v : β Ξ± Ξ ) β π mΞ΅ v β‘ g (index mΞ΅ v) π-ix (x β mΞ΅) new = π€ππ! x π-ix (x β mΞ΅) (old v) = π-ix mΞ΅ v π€ππ! (mvar πͺ mΞ΅) rewrite cong (ππ£ππ πͺ) (dext (π-ix mΞ΅)) = trans (sym β¨ππ£ππβ©) (cong (g β mvar πͺ) (tabβixβid mΞ΅)) π€ππ! (var v) = sym β¨π£ππβ© π€ππ! (_$_ a b) rewrite π€ππ! a | π€ππ! b = sym β¨πππβ© π€ππ! (Ζ_ a) rewrite π€ππ! a = sym β¨πππβ©
-
instantiation of the generic second-order metatheory with the syntax. The
Theory
module contains operations such as variable renamingπ£ππ
, variable substitutionπ€π¦π
, and special cases such as weakeningπ¨π : Ξ Ξ± Ξ β Ξ Ξ± (Ο β Ξ)
and single-variable substitution[_/]_ : Ξ Ξ² Ξ β Ξ Ξ± (Ξ² β Ξ) β Ξ Ξ± Ξ
. It also contains various correctness properties of the operations, such as the syntactic and semantic substitution lemmas. See here for an example of how these can be used.
The equational theory file contains the axiom declaration of the syntax, and the instantiation of the generic second-order equational reasoning library.
data _βΉ_β’_ββ_ : β π Ξ {Ξ±} β (π β· Ξ) Ξ± Ξ β (π β· Ξ) Ξ± Ξ β Set where
ΖΞ² : β
Ξ± β© Ξ² β β
Ξ± βΜ£ βΉ β
β’ (Ζ πβ¨ xβ β©) $ π ββ πβ¨ π β©
ΖΞ· : β
Ξ± β£ Ξ² βΜ£ βΉ β
β’ Ζ (π $ xβ) ββ π
π : MCtx
is an inductively-defined metavariable context that can be converted to a sorted family of metavariables. It is constructed as a sequence of second-order metavariable type declarations β
Ξ β β© Οβ β β
Ξ β β© Οβ β β¦ β
Ξ β β© Οβ βΜ£
, where Ο
is the type of the metavariable and Ξ
is the context of its parameters. Note that the last closing bracket must be βΜ£
. The Ξ β©
prefix can be omitted if the parameter context is empty, as in the case of ΖΞ·
and the second metavariable of ΖΞ²
above. Metavariables are referred to by the alphabetic de Bruijn indices π
, π
, etc., and parameters are specified between angle brackets β¨β¦β©
attached to the metavariable. Object variables are given by numeric de Bruijn indices xβ
, xβ
, etc. The axioms may themselves be in a nonempty object variable context, such as in the case of partial differentiation.
The equational reasoning library generated from the axioms allows one to prove equalities through a sequence of rewrite steps, including:
- application of an axiom with a particular instantiation of metavariables;
- application of a derived equation with a particular instantiation of metavariables;
- application of a rewrite step on a subexpression (congruence);
- application of a definitional or propositional equality.
For example, below is the proof that the partial derivative of a variable with respect to itself is 1, expressed using the syntax and axiomatisation of partial differentiation:
βid : β
β βΉ β * β β’ ββ xβ β π
βid = begin
ββ xβ ββ¨ cong[ thm πUβα΄Ώ withγ xβ γ ]inside ββ βα΅ β©β
ββ (xβ β π) ββ¨ ax ββ withγ π γ β©
π β
-
Like in standard Agda-style equational reasoning, the proof begins with
begin
and ends withβ
, and proof steps are written inββ¨ E β©
between the terms (orββ¨ E β©β
for symmetric rewrite). -
The
cong[ E ]inside C
step applies the equalityE
to a subexpression of the term specified by the congruence contextC
, which is just another term with an extra metavariable that can be used to denote the 'hole' in which the congruence is applied. Instead ofπ
,π
, the hole metavariable can be denotedβα΅
,βα΅
, etc. to make its role clear. For example, above we apply the right unit law under the partial differentiation operatorββ
. -
The
ax A withγ tβ β tβ β β¦ β tβ γ
step applies an instance of axiomA
with its metavariablesπ
,π
, β¦,π«
instantiated with termstβ
,tβ
, β¦,tβ
-- for example, above we apply the derivative-of-sum axiomββ : β * βΜ£ βΉ β * β β’ ββ (xβ β π) ββ π
with the metavariableπ
set to the termπ
. -
The
thm T withγ tβ β tβ β β¦ β tβ γ
step is similar to the axiom step, exceptT
is an established/derived equality, rather than an axiom. For example, the right unit law of commutative ringsπUβα΄Ώ
is derived from the left unit lawπUβα΄Έ
and the commutativity ofβ
, so it is applied as a theorem, rather than an axiom. A theorem without any metavariables (such asβid
itself) can be applied asthm T
.
As another example, consider the derivation of the right distributivity of β
over β
from the left distributivity and commutativity of β
. It shows the use of the double congruence step congβ[ Eβ ][ Eβ ]inside C
, where C
can contain two hole metavariables.
βDβα΄Ώ : β
* β β
* β β
* βΜ£ βΉ β
β’ (π β π) β π β (π β π ) β (π β π )
βDβα΄Ώ = begin
(π β π) β π ββ¨ ax βC withγ π β π β π γ β©
π β (π β π) ββ¨ ax βDβα΄Έ withγ π β π β π γ β©
(π β π) β (π β π) ββ¨ congβ[ ax βC withγ π β π γ ][ ax βC withγ π β π γ ]inside βα΅ β βα΅ β©
(π β π ) β (π β π ) β
A property of first-order logic is pushing a conjoined proposition under a universal quantifier from the right, derivable from the commutativity of β§
and the analogous left-hand side property. The proof proceeds by commuting the conjunction, applying the left-side property, then commuting back under the universal quantifier. Importantly, to be able to apply commutativity to the term π β§ πβ¨ xβ β©
(which contains a free variable bound by the outside quantifier), we need to make xβ
'available' in the hole by writing ββ² (βαΆβ¨ xβ β©)
for the congruence context (rather than simply ββ² βαΆ
).
β§Pβα΄Ώ : β
N β© * β β
* βΜ£ βΉ β
β’ (ββ² (πβ¨ xβ β©)) β§ π β ββ² (πβ¨ xβ β© β§ π)
β§Pβα΄Ώ = begin
(ββ² (πβ¨ xβ β©)) β§ π ββ¨ ax β§C withγ ββ² (πβ¨ xβ β©) β π γ β©
π β§ (ββ² (πβ¨ xβ β©)) ββ¨ ax β§Pβα΄Έ withγ π β πβ¨ xβ β© γ β©
ββ² (π β§ πβ¨ xβ β©) ββ¨ cong[ ax β§C withγ π β πβ¨ xβ β© γ ]inside ββ² (βαΆβ¨ xβ β©) β©
ββ² (πβ¨ xβ β© β§ π) β
For computational calculi like the STLC, equalities correspond to Ξ²Ξ·-equivalence of terms (assuming both axioms are given). For example, given the derived terms
plus : Ξ π (N β£ N β£ N) Ξ
plus = Ζ (Ζ (nrec xβ xβ (su xβ)))
uncurry : Ξ π ((Ξ± β£ Ξ² β£ Ξ³) β£ (Ξ± β Ξ²) β£ Ξ³) Ξ
uncurry = Ζ Ζ xβ $ fst xβ $ snd xβ
we can give an equational proof that uncurry $ plus $ β¨ su ze , su (su ze) β© β su (su (su ze))
. We first give a variant of Ξ²-reduction for double abstraction, which will allow us to apply plus
and uncurry
to both terms in one step.
ΖΞ²Β² : β
Ξ² Β· Ξ± β© Ξ³ β β
Ξ± β β
Ξ² βΜ£ βΉ β
β’ (Ζ (Ζ πβ¨ xβ β xβ β©)) $ π $ π β πβ¨ π β π β©
ΖΞ²Β² = begin
(Ζ (Ζ πβ¨ xβ β xβ β©)) $ π $ π
ββ¨ cong[ ax ΖΞ² withγ (Ζ πβ¨ xβ β xβ β©) β π γ ]inside βα΅ $ π β©
(Ζ πβ¨ xβ β π β©) $ π
ββ¨ ax ΖΞ² withγ (πβ¨ xβ β π β©) β π γ β©
πβ¨ π β π β©
β
Then, the calculational proof is as follows:
1+2 : β
β βΉ β
β’ uncurry $ plus $ β¨ su ze , su (su ze) β© β su (su (su ze))
1+2 = begin
uncurry $ plus $ β¨ su ze , su (su ze) β©
ββ¨ thm ΖΞ²Β² withγ xβ $ fst xβ $ snd xβ β plus β β¨ su ze , su (su ze) β© γ β©
plus $ fst β¨ su ze , su (su ze) β© $ snd β¨ su ze , su (su ze) β©
ββ¨ congβ[ ax fΞ² withγ su ze β su (su ze) γ ][
ax sΞ² withγ su ze β su (su ze) γ ]inside plus $ βα΅ $ βα΅ β©
plus $ su ze $ su (su ze)
ββ¨ thm ΖΞ²Β² withγ nrec xβ xβ (su xβ) β su ze β su (su ze) γ β©
nrec (su ze) (su (su ze)) (su xβ)
ββ¨ ax suΞ² withγ su (su ze) β su xβ β ze γ β©
su (nrec ze (su (su ze)) (su xβ))
ββ¨ cong[ ax zeΞ² withγ su (su ze) β su xβ γ ]inside su βα΅ β©
su (su (su ze))
β
Surrounding a type, term, or equation with {β¦}
marks it as a derived construct: instead of being added to the inductive declaration of types, terms, or axioms, it is listed as an Agda value declaration of the appropriate Agda type, with ?
in place of the implementation. For example, a let
-declaration can be desugared to application, so instead of adding it as a separate operator, we mark it (along with its equational property) as derived:
syntax STLC | Ξ
type
N : 0-ary
_β£_ : 2-ary | r30
term
app : Ξ± β£ Ξ² Ξ± -> Ξ² | _$_ l20
lam : Ξ±.Ξ² -> Ξ± β£ Ξ² | Ζ_ r10
{letd : Ξ± Ξ±.Ξ² -> Ξ²}
theory
(ΖΞ²) b : Ξ±.Ξ² a : Ξ± |> app (lam(x.b[x]), a) = b[a]
(ΖΞ·) f : Ξ± β£ Ξ² |> lam (x. app(f, x)) = f
{(lΞ²) b : Ξ±.Ξ² a : Ξ± |> letd (a, x.b[x]) = b[a]}
This will compile to the Agda declaration
-- Derived operations
letd : Ξ π Ξ± Ξ β Ξ π Ξ² (Ξ± β Ξ) β Ξ π Ξ² Ξ
letd = ?
that can be implemented by hand (as letd a b = (Ζ b) $ a
), and similarly for the 'theorem'
-- Derived equations
lΞ² : β
Ξ± β© Ξ² β β
Ξ± βΜ£ βΉ β
β’ letd π (πβ¨ xβ β©) β πβ¨ π β©
lΞ² = ?
that can be implemented as lΞ² = ax ΖΞ² withγ πβ¨ xβ β© β π γ
since the declaration desugars (definitionally) to an application.
It is possible for not derived constructs to depend on derived ones. For example, the axiomatisation of partial differentiation involves the special cases of differentiation with respect to the first and second variable. We mark them as operations derived from pdiff
, and use them as normal in the axioms:
term
pdiff : *.* * -> * | β_β£_
{d0 : *.* -> *.* | ββ_}
{d1 : (*,*).* -> (*,*).* | ββ_}
theory
(ββ) a : * |> x : * |- d0 (add (x, a)) = one
(ββ) a : * |> x : * |- d0 (mult(a, x)) = a
...
Note also that derived operations (like d0
and d1
) may have return sorts in an extended context, since they are defined by the user, rather than extracted from the generic framework. Non-derived operators must have return types without bound variables.
The library is also very suitable for reasoning about unsorted and first-order syntaxes, such as algebraic structures. While the full power of the generated metatheory is not needed (e.g. variable capture is not possible without binders), the notions of term- and equation-in-context and the equational reasoning framework are applicable. Furthermore, many second-order syntaxes are minor extensions of first-order ones (first-order logic is propositional logic extended with quantifiers, the axiomatisation of partial differentiation involves extending a commutative ring with a partial differentiation operation) so results about the first-order fragments can be transferred to the extended settings.
It is straightforward to capture algebraic structures such as monoids in a syntax description:
syntax Monoid | M
term
unit : * | Ξ΅
add : * * -> * | _β_ l20
theory
(Ξ΅Uβα΄Έ) a |> add (unit, a) = a
(Ξ΅Uβα΄Ώ) a |> add (a, unit) = a
(βA) a b c |> add (add(a, b), c) = add (a, add(b, c))
The lack of a type
declaration marks the syntax as un(i)sorted with sort *
. For convenience, standard algebraic properties like unit laws and associativity are predefined in the library, so we can instead write:
syntax Monoid | M
term
unit : * | Ξ΅
add : * * -> * | _β_ l20
theory
'unit' unit of 'add'
'add' associative
The two properties internally desugar to the explicitly written axioms. The supported properties are listed below, where op0
, op1
and op2
denote constants, unary and binary operators, respectively.
'op2' associative, commutative, idempotent, involutive
'op0' [left|right] unit of 'op2'
'op0' [left|right] annihilates 'op2'
'op1' [left|right] inverse of 'op2' with 'op0'
(op1
is the inverse/negation operator,op0
is the unit)'op2' [left|right] distributes over 'op2'
'op2' [left|right] absorbs 'op2'
'op2' and 'op2' absorptive
(both operators absorb each other)
Several properties may be directed: left unit, right distributivity, etc. If the corresponding binary operator is commutative, the directions are interderivable. In this case, the library will only list the left direction as an axiom, and generates the derivation of the right axiom automatically. Thus, for a commutative monoid
syntax CommMonoid | CM
term
unit : * | Ξ΅
add : * * -> * | _β_ l20
theory
'unit' unit of 'add'
'add' associative, commutative
the generated axioms will only include the left unit law
data _βΉ_β’_ββ_ : β π Ξ {Ξ±} β (π β· CM) Ξ± Ξ β (π β· CM) Ξ± Ξ β Set where
Ξ΅Uβα΄Έ : β
* βΜ£ βΉ β
β’ Ξ΅ β π ββ π
βA : β
* β β
* β β
* βΜ£ βΉ β
β’ (π β π) β π ββ π β (π β π )
βC : β
* β β
* βΜ£ βΉ β
β’ π β π ββ π β π
and the derivation of Ξ΅Uβα΄Ώ
is predefined:
Ξ΅Uβα΄Ώ : β
* βΜ£ βΉ β
β’ π β Ξ΅ β π
Ξ΅Uβα΄Ώ = tr (ax βC withγ π β Ξ΅ γ) (ax Ξ΅Uβα΄Έ withγ π γ)
In fact, the right distributivity example βDβα΄Ώ
given in Equational reasoning is automatically derived from left distributivity and commutativity of β
.
This saves a lot of boilerplate defining symmetric versions of properties for commutative operators.
The syntax description language implements an Agda-like module system for extending and combining syntaxes. This is very convenient for building up algebraic hierarchies or adding new constructs to computational calculi.
Building on top of an existing base syntax is done using the extends
keyword. In the simplest case, the keyword is followed by a comma-separated list of file names that get recursively interpreted first and combined with the new constructs. For example, a group is a monoid with an inverse operator:
syntax Group | G extends monoid
term
neg : * -> * | β_ r40
theory
'neg' inverse of 'add' with 'unit'
By describing the introduction and elimination terms, and Ξ²Ξ·-equivalence rules of computational constructs independently, we can extend a base syntax (like STLC) with any number of new types and operations and 'mix and match' language features as we wish. For example, the 'typed Ξ»-calculus' is the extension of the simply typed Ξ»-calculus with products, sums, unit and empty types, and natural numbers (which are individually described in their respective files):
syntax TLC | Ξ extends stlc, unit, prod, empty, sum, nat
This is equivalent to the following syntax description:
syntax TLC | Ξ
type
N : 0-ary
_β£_ : 2-ary | r30
π : 0-ary
_β_ : 2-ary | l40
π : 0-ary
_β_ : 2-ary | l30
term
app : Ξ± β£ Ξ² Ξ± -> Ξ² | _$_ l20
lam : Ξ±.Ξ² -> Ξ± β£ Ξ² | Ζ_ r10
unit : π
pair : Ξ± Ξ² -> Ξ± β Ξ² | β¨_,_β©
fst : Ξ± β Ξ² -> Ξ±
snd : Ξ± β Ξ² -> Ξ²
abort : π -> Ξ±
inl : Ξ± -> Ξ± β Ξ²
inr : Ξ² -> Ξ± β Ξ²
case : Ξ± β Ξ² Ξ±.Ξ³ Ξ².Ξ³ -> Ξ³
ze : N
su : N -> N
nrec : N Ξ± (Ξ±,N).Ξ± -> Ξ±
theory
(ΖΞ²) b : Ξ±.Ξ² a : Ξ± |> app (lam(x.b[x]), a) = b[a]
(ΖΞ·) f : Ξ± β£ Ξ² |> lam (x. app(f, x)) = f
(πΞ·) u : π |> u = unit
(fΞ²) a : Ξ± b : Ξ² |> fst (pair(a, b)) = a
(sΞ²) a : Ξ± b : Ξ² |> snd (pair(a, b)) = b
(pΞ·) p : Ξ± β Ξ² |> pair (fst(p), snd(p)) = p
(πΞ·) e : π c : Ξ± |> abort(e) = c
(lΞ²) a : Ξ± f : Ξ±.Ξ³ g : Ξ².Ξ³ |> case (inl(a), x.f[x], y.g[y]) = f[a]
(rΞ²) b : Ξ² f : Ξ±.Ξ³ g : Ξ².Ξ³ |> case (inr(b), x.f[x], y.g[y]) = g[b]
(cΞ·) s : Ξ± β Ξ² c : (Ξ± β Ξ²).Ξ³ |> case (s, x.c[inl(x)], y.c[inr(y)]) = c[s]
(zeΞ²) z : Ξ± s : (Ξ±,N).Ξ± |> nrec (ze, z, r m. s[r,m]) = z
(suΞ²) z : Ξ± s : (Ξ±,N).Ξ± n : N |> nrec (su (n), z, r m. s[r,m])
= s[nrec (n, z, r m. s[r,m]), n]
We can have more control over what names we import and how via the hiding
, using
, deriving
and renaming
modifiers. In these cases, the imports are listed one-by-one in the syntax
block with the modifiers (separated by ;
) in parentheses. For example:
syntax Ext extends
- syn1 (using op1, op2, eq3; renaming eq1 to ax1; deriving eq3)
- syn2 (hiding eq2; renaming op0:Ξ΅ to const:c, op3:β to op4:β :l40)
The first line imports the operators op1
and op2
(and their corresponding symbols, if they exist) and axiom eq
, renames eq1
to ax1
, and marks eq3
as a derivable property (presumably using the new axioms of the Ext
module). The second line imports everything (types, operators, symbols, equations) other than eq2
, and gives op0
and op3
new names: op0
and its symbol Ξ΅
get renamed to const
and c
respectively, while op3
and β
get renamed to op4
and β
with the fixity of β
changed to l40
. The renaming of symbols also extends to occurrences of symbols in axiom names: for example, an axiom like Ξ΅Uβα΄Έ
in syn2
would automatically be renamed to cUβ α΄Έ
in Ext
.
As a concrete example, consider the algebraic structure of a semiring, consisting of a commutative (additive) monoid and a second (multiplicative) monoid satisfying two-way distributivity and annihilation. This 'recipe' can be concisely expressed using imports, renaming of names and symbols, and property specifications:
syntax Semiring | SR extends
- cmonoid (renaming unit:Ξ΅ to zero:π)
- monoid (renaming unit:Ξ΅ to one:π, add:β to mult:β:l30)
theory
'mult' distributes over 'add'
'zero' annihilates 'mult'
The resulting description is equivalent to the following, with πUβα΄Ώ
derived automatically:
syntax Semiring | SR
type
* : 0-ary
term
zero : * | π
add : * * -> * | _β_ l20
one : * | π
mult : * * -> * | _β_ l30
theory
(πUβα΄Έ) a |> add (zero, a) = a
(πUβα΄Ώ) a |> add (a, zero) = a
(βA) a b c |> add (add(a, b), c) = add (a, add(b, c))
(βC) a b |> add(a, b) = add(b, a)
(πUβα΄Έ) a |> mult (one, a) = a
(πUβα΄Ώ) a |> mult (a, one) = a
(βA) a b c |> mult (mult(a, b), c) = mult (a, mult(b, c))
(βDβα΄Έ) a b c |> mult (a, add (b, c)) = add (mult(a, b), mult(a, c))
(βDβα΄Ώ) a b c |> mult (add (a, b), c) = add (mult(a, c), mult(b, c))
(πXβα΄Έ) a |> mult (zero, a) = zero
(πXβα΄Ώ) a |> mult (a, zero) = zero
This may be further extended to rings and commutative rings as follows:
syntax Ring | R extends semiring
term
neg : * -> * | β_ r50
theory
'neg' inverse of 'add' with 'zero'
syntax CommRing | CR extends ring
theory
'mult' commutative
The equational theory corresponding to commutative rings only includes left-side axioms, since every right-side property (unit laws, annihilation law, distributivity, negation) is derived automatically.