Skip to content

Agda formalisation of second-order abstract syntax

License

Notifications You must be signed in to change notification settings

DimaSamoz/agda-soas

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

45 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SOAS: Second-Order Abstract Syntax

An Agda formalisation framework for second-order languages.

Accompanies the POPL 2022 paper Formal Metatheory of Second-Order Abstract Syntax.

Quick start

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.

Introduction

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.

Directory structure

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.

Usage

Syntax description language

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.

Agda formalisation

The script generates three Agda files (two if an equational theory is not given).

Signature.agda

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.

Syntax.agda

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.

Equality.agda

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.

Equational reasoning

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 equality E to a subexpression of the term specified by the congruence context C, 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 axiom A with its metavariables π”ž, π”Ÿ, …, 𝔫 instantiated with terms t₁, 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, except T 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 as thm 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))
  ∎

Additional features

Derived constructions

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.

Algebraic properties

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.

Module system

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.

About

Agda formalisation of second-order abstract syntax

Resources

License

Stars

Watchers

Forks

Packages

No packages published