<<

Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules Jesper Cockx Department of Software Technology, TU Delft, The Netherlands https://jesper.sikanda.be [email protected]

Abstract Dependently typed languages such as Coq and Agda can statically guarantee the correctness of our proofs and programs. To provide this guarantee, they restrict users to certain schemes – such as strictly positive datatypes, complete case analysis, and well-founded induction – that are known to be safe. However, these restrictions can be too strict, making programs and proofs harder to write than necessary. On a higher level, they also prevent us from imagining the different ways the language could be extended. In this paper I show how to extend a dependently typed language with user-defined higher-order non-linear rewrite rules. Rewrite rules are a form of equality reflection that is applied automatically by the typechecker. I have implemented rewrite rules as an extension to Agda, and I give six examples how to use them both to make proofs easier and to experiment with extensions of . I also show how to make rewrite rules interact well with other features of Agda such as η-equality, implicit , data and record types, irrelevance, and universe level polymorphism. Thus rewrite rules break the chains on computation and put its power back into the hands of its rightful owner: yours.

2012 ACM Subject Classification Theory of computation → Rewrite systems; Theory of computa- tion → Equational logic and rewriting; Theory of computation → Type theory

Keywords and phrases Dependent types, Proof assistants, Rewrite rules, Higher-order rewriting, Agda

Digital Object Identifier 10.4230/LIPIcs.TYPES.2019.2

Supplementary Material The official documentation of rewrite rules in Agda is available in the user manual at https://agda.readthedocs.io/en/v2.6.1/language/rewriting.html. The full source code of Agda (including rewrite rules) is available on Github at https://github.com/agda/agda/.

1 Introduction

In the tradition of Martin-Löf Type Theory [19], each type former is declared by four sets of rules: The formation rule, e.g. Bool : The introduction rules, e.g. true : Bool and false : Bool The elimination rules, e.g. if P : Bool → Set, b : Bool, pt : P true, and pf : P false, then if b then pt else pf : P b The computation rules, e.g. if true then pt else pf = pt and if false then pt else pf = pf

When working in a proof assistant or dependently typed programming language, we usually do not introduce new types directly by giving these rules. That would be very unsafe, as there is no easy way to check that the given rules make sense. Instead, we introduce new rules through schemes that are well-known to be safe, such as strictly positive datatypes, complete case analysis, and well-founded induction.

© Jesper Cockx; licensed under Creative Commons License CC-BY 25th International Conference on Types for Proofs and Programs (TYPES 2019). Editors: Marc Bezem and Assia Mahboubi; Article No. 2; pp. 2:1–2:27 Leibniz International Proceedings in Informatics Schloss Dagstuhl – Leibniz-Zentrum für Informatik, Dagstuhl Publishing, Germany 2:2 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

However, users of dependently typed languages or researchers who are experimenting with adding new features to them might find working within these schemes too restrictive. They might be tempted to use postulate to simulate the formation, introduction, and elimination rules of new type formers. Yet in intensional type theories there is one thing that cannot be added by using postulate: the computation rules. This paper shows how to extend a dependently typed language with user-defined re- write rules, allowing the user to extend the definitional equality of the language with new computation rules. Concretely, I extend the Agda language [22] with a new op- tion --rewriting. When this option is enabled, you can register a proof (or a postulate) p : ∀x1 . . . xn → f u1 . . . un ≡ v (where the ∀ quantifies over the free variables x1 . . . xn of u1 . . . un and v, and ≡ is Agda’s built-in identity type) as a rewrite rule with a pragma {-# REWRITE p #-}. From this point on, Agda will automatically reduce instances of the left-hand side f u1 . . . un (i.e. for specific values of x1 . . . xn) to the corresponding instance of v. As a silly example, if f : A → A and p : ∀x → f x ≡ x, then the rewrite rule will replace any application f u with u, effectively turning f into the identity λx → x (which is the Agda for the lambda term λx. x). Since rewrite rules enable you as the user of Agda to turn propositional (i.e. proven) equalities into definitional (i.e. computational) ones, rewrite rules can be seen as a restricted version of the equality reflection rule from extensional type theory, thus they do not impact logical soundness of Agda directly. However, they can break other important properties of Agda such as confluence of reduction and strong normalization. Checking these properties automatically is outside of the scope of this paper, but some potential approaches are discussed in Sect. 6. Instead, the main goal of this paper is to specify in detail one possible way to add a general notion of rewrite rules to a real-world dependently typed language. This is meant to serve at the same time as a specification of how rewrite rules are implemented in Agda and also as a guideline how they could be added to other languages.

Contributions

I define a core type theory based on Martin-Löf’s intensional type theory extended with user-defined higher-order non-linear rewrite rules. I describe how rewrite rules interact with several common features of dependently typed languages, such as η-equality, data and record types, parametrized modules, proof irrelevance, universe level polymorphism, and constraint solving for metavariables. I implement rewrite rules as an extension to Agda and show in six examples how to use them to make writing programs and proofs easier and to experiment with new extensions to Agda.

The official documentation of rewrite rules in Agda is available in the user manual1. The source code of Agda is available on Github2, the code dealing with rewrite rules specifically can be found in the files Rewriting.hs3 (418 lines), NonLinPattern.hs4 (329 lines), NonLinMatch.hs5 (422 lines), and various other places in the Agda codebase.

1 https://agda.readthedocs.io/en/v2.6.1/language/rewriting.html 2 https://github.com/agda/agda/ 3 https://github.com/agda/agda/blob/master/src/full/Agda/TypeChecking/Rewriting.hs 4 https://github.com/agda/agda/blob/master/src/full/Agda/TypeChecking/Rewriting/ NonLinPattern.hs 5 https://github.com/agda/agda/blob/master/src/full/Agda/TypeChecking/Rewriting/ NonLinMatch.hs J. Cockx 2:3

Note on the development of rewrite rules in Agda. When the development of rewrite rules in Agda started in 2016, it was expected to be used mainly by type theory researchers to experiment with new computation rules without modifying the implementation of the language itself. For this use case, accepting a large of rewrite rules is more important than having strong guarantees about (admittedly important) metatheoretical properties such as subject reduction, confluence, or termination, which can be checked by hand if necessary. This is the basis for the rewrite rules as described in the paper. More recently, Agda users also started using this rewriting facility to enhance Agda’s conversion checker with new (proven) equalities, as showcased by the examples in Sect. 2.1 and Sect. 2.2. For this class of users having strong guarantees about subject reduction, confluence and termination is more important. In the future, I would like to extend the support for these users further as outlined in Sect. 6.

Outline of the paper. Sect. 2 consists of examples of how to use rewrite rules to go beyond the usual boundaries set by Agda and define your own computation rules. After these examples, Sect. 3 shows more generally how to add rewrite rules to a dependently typed language, and Sect. 4 shows how rewrite rules interact with other features of Agda. Related work and future work are discussed in Sect. 5 and Sect. 6, and Sect. 7 concludes.

2 Using rewrite rules

With the introduction out of the way, let us start with some examples of things you can do with rewrite rules. I hope at least one example gives you the itch to try rewrite rules for yourself. There are some restrictions on what kind of equality proofs can be turned into rewrite rules, which will be explained later in general. Until then, the examples should give an idea of the kind of things that are possible. All examples in this section are accepted by Agda 2.6.1 [1]. We start with some basic options and imports. For the purpose of this paper, the two most important ones are the --rewriting flag and the import of Agda.Builtin.Equality.Rewrite, which are both required to make rewrite rules work. Meanwhile, the --prop flag enables Agda’s Prop universe6 [16], which will be used in some of the examples.

{−# OPTIONS --rewriting --prop #−}

open import Agda.Primitive open import Agda.Builtin.Bool open import Agda.Builtin.Nat open import Agda.Builtin.List open import Agda.Builtin.Equality open import Agda.Builtin.Equality.Rewrite

The examples in this paper make use of generalizable variables7 to avoid writing many quantifiers and make the code more readable.

6 https://agda.readthedocs.io/en/v2.6.1/language/prop.html 7 https://agda.readthedocs.io/en/v2.6.1/language/generalization-of-declared-variables. html

T Y P E S 2 0 1 9 2:4 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

variable

`` 1 `2 `3 `4 : Level ABC : Set ` PQ : A → Set ` xyz : A fgh :( x : A) → Px

bb 1 b2 b3 : Bool klmn : Nat xs ys zs : List A R : A → A → Prop

We use the following helper function to annotate terms with their types:

El :( A : Set `) → A → A El Ax = x

infix5 El syntax El Ax = x ∈ A

To avoid reliance on external libraries, we also need two basic properties of equality:

cong :( f : A → B) → x ≡ y → fx ≡ fy cong f refl = refl

transport :( P : A → Set `) → x ≡ y → Px → Py transport P refl p = p

2.1 Overlapping pattern matching To start, let us look at a question that is asked by almost every newcomer to Agda: why does 0 + m compute to m, but m + 0 does not? Similarly, why does (suc m) + n compute to suc (m + n) but m +( suc n) does not? This problem manifests itself for example when trying to prove commutativity of _+_ (the lack of highlighting is a sign that the code is not accepted by Agda):

+comm : m + n ≡ n + m +comm {m = zero} = refl +comm {m = suc m} = cong suc (+comm {m = m})

Here Agda complains that n 6= n + zero. The problem is usually solved by proving the equations m + 0 ≡ m and m + (suc n) ≡ suc (m + n) and using an explicit rewrite8 statement in the proof of +comm. Despite solving the problem, this solution is rather disappointing: if Agda can tell that 0 + m computes to m, why not m + 0? During my master thesis, I worked on overlapping computation rules [14] to make this problem go away without adding any explicit rewrite statements. By using rewrite rules, we can simulate this solution in Agda. First, we need to prove that the equations we want hold as propositional equalities:

8 Agda’s rewrite keyword should not be confused with rewrite rules, which are added by a REWRITE pragma. J. Cockx 2:5

+zero : m + zero ≡ m +zero {m = zero} = refl +zero {m = suc m} = cong suc +zero

+suc : m +( suc n) ≡ suc (m + n) +suc {m = zero} = refl +suc {m = suc m} = cong suc +suc

Then we mark the equalities as rewrite rules with a REWRITE pragma:

{−# REWRITE +zero +suc #−}

Now the proof of commutativity works exactly as we wrote before:

+comm : m + n ≡ n + m +comm {m = zero} = refl +comm {m = suc m} = cong suc (+comm {m = m})

Without rewrite rules there is no way to make this proof go through unchanged: it is essential that _+_ computes both on its first and second arguments, but there is no way to define_ +_ in such a way using Agda’s regular pattern matching.

2.2 New equations for neutral terms Allais, McBride, and Boutillier [2] extend classic functions on lists such as , _++_ (concatenation), and fold with new equational rules for neutral expressions. In Agda, we can prove these rules and then add them as rewrite rules. For example, here are their rules for map and_ ++_:

map :( A → B) → List A → List B map f []=[] map f (x :: xs)=( fx )::( map f xs)

infixr5 _++_ _++_ : List A → List A → List A [] ++ ys = ys (x :: xs) ++ ys = x ::( xs ++ ys)

map−id : map (λ x → x) xs ≡ xs map−id {xs = []} = refl map−id {xs = x :: xs} = cong (x ::_) map−id

map−fuse : map f (map g xs) ≡ map (λ x → f (gx )) xs map−fuse {xs = []} = refl map−fuse {xs = x :: xs} = cong (_ ::_) map−fuse

map−++: map f (xs ++ ys) ≡ (map f xs) ++( map f ys) map−++ {xs = []} = refl map−++ {xs = x :: xs} = cong (_ ::_)( map−++ {xs = xs})

{−# REWRITE map−id map−fuse map−++# −}

T Y P E S 2 0 1 9 2:6 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

These rules look simple, but can be quite powerful. For example, below we show that the expression map swap (map swap xs ++ map swap ys) reduces to xs ++ ys, without requiring any induction on lists.

record _×_ (AB : Set): Set where constructor _, _ field fst : A snd : B open _×_

swap : A × B → B × A swap (x , y)= y , x

test : map swap (map swap xs ++ map swap ys) ≡ xs ++ ys test = refl

To compute the left-hand side of the equation to the right-hand side, Agda makes use of map−++ (step1), map−fuse (step2), built-in η-equality of _ × _ (step3), the definition of swap (step4), and finally the map−id rewrite rule (step5).

step1 : map swap (map swap xs ++ map swap ys) ≡ map swap (map swap xs) ++ map swap (map swap ys)

step1 = refl

step2 : map swap (map swap xs) ≡ map (λ x → swap (swap x)) xs

step2 = refl

step3 : map (λ x → swap (swap x)) xs ≡ map (λ x → swap (swap (fst x , snd x))) xs

step3 = refl

step4 : map (λ x → swap (swap (fst x , snd x))) xs ≡ map (λ x → (fst x , snd x)) xs

step4 = refl

step5 : map (λ x → (fst x , snd x)) xs ≡ xs

step5 = refl

2.3 Higher inductive types The original motivation for adding rewrite rules to Agda had little to do with adding new computation rules to existing functions as in the previous examples. Instead, its purpose was to experiment with defining higher inductive types [30]. In particular, it was meant as an alternative for people using clever (but horrible) hacks to make higher inductive types compute.9 A higher inductive type is similar to a regular inductive type D with some additional path constructors, which construct an of the identity type a ≡ b where a : D and b : D. A classic example is the Circle type, which has one regular constructor base and one path constructor loop (note that Set in Agda corresponds to Type rather than hSet from HoTT):

9 https://homotopytypetheory.org/2011/04/23/running-circles-around-in-your-proof-assis- tant/ J. Cockx 2:7

postulate Circle : Set base : Circle loop : base ≡ base

postulate Circle−elim :( P : Circle → Set `)( base∗ : P base)( loop∗ : transport P loop base∗≡ base∗) → (x : Circle) → Px elim−base : ∀ (P : Circle → Set `) base∗ loop∗→ Circle−elim P base∗ loop∗ base ≡ base∗ {−# REWRITE elim−base #−}

To specify the computation rule for Circle−elim applied to loop, we need the dependent version of cong, which is called apd in the book [30].

apd :( f :( x : A) → Px )( p : x ≡ y) → transport Pp (fx ) ≡ fy apd f refl = refl

postulate elim−loop : ∀ (P : Circle → Set `) base∗ loop∗→ apd (Circle−elim P base∗ loop∗) loop ≡ loop∗ {−# REWRITE elim−loop #−}

Without the rewrite rule elim−base, the type of elim−loop is not well-formed. So without rewrite rules, it is impossible to even state the computation rule of Circle−elim on the path constructor loop without adding extra transports that would influence its computational behaviour.

2.4 Quotient types One of the well-known weak spots of intensional type theory is its poor handling of quotient types. One of the more promising attempts at adding quotients to Agda is by Guillaume Brunerie in the initiality project10, which uses a combination of rewrite rules and Agda’s Prop universe. Unlike Prop in Coq or hProp in HoTT (but like sProp in Coq), Prop in Agda is a universe of definitionally irrelevant , which means any two proofs of a type in Prop are definitionally equal. Before I can show this definition of the quotient type, we first need to define the Prop- . valued equality type _=_. We also define its corresponding notion of transport, which has to be postulated due to current limitations in the implementation of Prop. To make transportR compute in the expected way, we add it as a rewrite rule transportR−refl. . data _=_ {A : Set `} (x : A): A → Prop ` where . refl : x = x

postulate . transportR :( P : A → Set `) → x = y → Px → Py transportR−refl : transportR {x = x}{ y = x} P refl z ≡ z {−# REWRITE transportR−refl #−}

Note that the rewrite rule transportR−refl is non-linear in its two implicit arguments x and y.

10 https://github.com/guillaumebrunerie/initiality/blob/reflection/quotients.agda

T Y P E S 2 0 1 9 2:8 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

Now we are ready to define the quotient type _//_. Given a type A and a Prop-valued relation R : A → A → Prop, the type A//R consists of elements proj x where x : A, and proj x is equal to proj y if and only if R x y holds.

postulate _//_ :( A : Set `)( R : A → A → Prop) → Set ` proj : A → A // R . quot : Rxy → proj {R = R} x = proj {R = R} y

The elimination principle //−elim allows us to define functions that extract an element of A from a given element of A//R , provided a proof quot∗ that the function respects the equality on A//R . The computation rule //−beta allows //−elim to compute when it is applied to a proj x.

//−elim :( P : A // R → Set `)( proj∗ :( x : A) → P (proj x)) . → (quot∗ : {xy : A} (r : Rxy ) → transportR P (quot r)( proj∗ x) = proj∗ y) → (x : A // R) → Px //−beta : {R : A → A → Prop} (P : A // R → Set `)( proj∗ :( x : A) → P (proj x)) . → (quot∗ : {xy : A} (r : Rxy ) → transportR P (quot r)( proj∗ x) = proj∗ y) →{ u : A}→ //−elim P proj∗ quot∗ (proj u) ≡ proj∗ u {−# REWRITE //−beta #−}

Compared to the more standard way of defining the quotient type as a higher inductive type, this definition behaves better with respect to definitional equality: the quot∗ to the eliminator is definitionally irrelevant, so it does not matter what equality proof we give. Consequently, there is no need to add an additional constructor to truncate the quotient type.

2.5 Exceptional type theory First-class exceptions are a common feature of object-oriented programming languages such as Java, but in the world of pure functional languages they are usually frowned upon. However, recently Pédrot and Tabareau have proposed an extension of Coq with first-class exceptions [23]. With the exceptional power of rewrite rules, we can also encode (part of) their system in Agda. First, we postulate a type Exc with any kinds of exceptions we might want to use (here we just have a single runtimeException for simplicity). We then add the possibility to raise an exception, producing an element of an arbitrary type A.

postulate Exc : Set runtimeException : Exc raise : Exc → A

Note that raise makes the type theory inconsistent. In their paper, Pédrot and Tabareau show how to build a safe version of exceptions on top of this system, using parametricity to enforce that all exceptions are caught locally. Here that part is omitted for brevity. By adding the appropriate rewrite rules for each type former, we can ensure that exceptions are propagated appropriately. For positive types such as Nat, exceptions are propagated outwards, while for negative types such as function types, exceptions are propagated inwards. J. Cockx 2:9

postulate raise−suc : {e : Exc}→ suc (raise e) ≡ raise e raise−fun : {e : Exc}→ raise {A =( x : A) → Px } e ≡ λ x → raise {A = Px } e {−# REWRITE raise−suc raise−fun #−}

To complete the system, we add the ability to catch exceptions at specific types. This takes the shape of an eliminator with one additional method for handling the case where the element under scrutiny is of the form raise e.

postulate catch−Bool :( P : Bool → Set `)( pt : P true)( pf : P false) → (h : ∀ e → P (raise e)) → (b : Bool) → Pb

catch−true : ∀ (P : Bool → Set `) pt pfh → catch−Bool P pt pfh true ≡ pt catch−false : ∀ (P : Bool → Set `) pt pfh → catch−Bool P pt pfh false ≡ pf catch−exc : ∀ (P : Bool → Set `) pt pfhe → catch−Bool P pt pfh (raise e) ≡ he {−# REWRITE catch−true catch−false catch−exc #−}

As shown by this example, rewrite rules can be used to extend Agda with new primitive operations, including ones that compute according to the type of their arguments. Currently the user has to add new rewrite rules manually for each datatype and function , so using this in practice is quite tedious. In the future, it might be possible to leverage Agda’s reflection framework to generate these rewrite rules automatically.

2.6 Observational equality Rewrite rules also allow us to define type constructors that compute according to the type they are applied to. This is a core part of observational type theory (OTT) [3]. OTT replaces the usual identity type with an observational equality type (here called _=∼_) that computes according to the type of the elements being compared. For example, an equality proof between pairs of type (a,b ) =∼ (c,d ) is a pair of proofs, one of type a =∼ c and one of type b =∼ d. Below, I show how to extend Agda with a fragment of OTT. Since OTT has a proof- irrelevant equality type, I use Agda’s Prop to get the same effect. First, we need some basic types in Prop:

record >{ `} : Prop ` where constructor tt

data ⊥{ `} : Prop ` where

record _∧_ (X : Prop `1 )( Y : Prop `2 ): Prop (`1 t `2 ) where constructor _, _ field fst : X snd : Y open _∧_

The open statement makes the constructor and the fields of the records available in the remainder of the module. The central type of OTT is observational equality _=∼_, which should compute according to the types of the elements being compared. Here I give the computation rules for Bool and for function types:

T Y P E S 2 0 1 9 2:10 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

infix6 _=∼_ postulate

_=∼_ : {A : Set `1 }{ B : Set `2 }→ A → B → Prop (`1 t `2 )

postulate refl−Bool :( Bool =∼ Bool) ≡> refl−true :( true =∼ true) ≡> refl−false :( false =∼ false) ≡> conflict−tf :( true =∼ false) ≡⊥ conflict−ft :( false =∼ true) ≡⊥ {−# REWRITE refl−Bool refl−true refl−false conflict−tf conflict−ft #−}

postulate cong−Π :(( x : A) → Px ) =∼ ((y : B) → Qy ) ≡ (B =∼ A) ∧ ((x : A)(y : B) → y =∼ x → Px =∼ Qy )

cong−λ : {A : Set `1 }{ B : Set `2 }{ P : A → Set `3 }{ Q : B → Set `4 } → (f :( x : A) → Px )( g :( y : B) → Qy ) → ((λ x → fx ) =∼ (λ y → gy )) ≡ ((x : A)( y : B)( x=∼y : x =∼ y) → fx =∼ gy ) {−# REWRITE cong−Π cong−λ #−}

According to cong−Π, an equality proof between function types computes to a pair of equality proofs between the domains and the respectively. Though not necessary, it is convenient to swap the sides of the equality proofs in contravariant positions (B ≡ A and y ≡ x). Meanwhile, an equality proof between two functions computes to an equality proof between the functions applied to heterogeneously equal variables x : A and y : B. To reason about equality proofs, OTT adds two more notions: coercion and cohesion. Coercion _[_i transforms an element from one type to the other when both types are observationally equal, and cohesion _||_ states that coercion is computationally the identity.

infix 10 _[_i _||_

postulate _[_i : A → (A =∼ B) → B _||_ :( x : A)( Q : A =∼ B) → (x ∈ A) =∼ (x [ Q i∈ B)

Here the ∈ annotations are just there to help Agda’s type algorithm. Again, we need rewrite rules to make sure coercion computes in the right way when applied to specific type constructors. On the other hand, We do not need rewrite rules for coherence since the result is of type _ =∼ _ which is a Prop, so the proof is anyway irrelevant. Coercing an element from Bool to Bool is easy.

postulate coerce−Bool :( Bool=∼Bool : Bool =∼ Bool) → b [ Bool=∼Bool i≡ b {−# REWRITE coerce−Bool #−}

To coerce a function from (x : A) → P x to (y : B) → Q y we need to:

1. Coerce the input from y : B to x : A 2. Apply the function to get an element of type P x 3. Coerce the output back to an element of Q y J. Cockx 2:11

In the last step, we need to use coherence to show that x and y are (heterogeneously) equal.

postulate

coerce−Π : {A : Set `1 }{ B : Set `2 }{ P : A → Set `3 }{ Q : B → Set `4 }{ f :( x : A) → Px } → (ΠAP=∼ΠBQ :(( x : A) → Px ) =∼ ((y : B) → Qy )) → f [ ΠAP=∼ΠBQ i ≡ (λ (y : B) → let B=∼A = fst ΠAP=∼ΠBQ x = y [ B=∼A i Px=∼Qy = snd ΠAP=∼ΠBQxy (_||_ {B = A} yB =∼A) in fx [ Px=∼Qy i∈ Qy ) {−# REWRITE coerce−Π #−} Here the syntax {B = A} instantiates the implicit argument B of _||_ to the value A. Of course this is just a fragment of the whole system, but implementing all of OTT would go beyond the scope of this paper. In principle, observational equality can be used as a full replacement for Agda’s built-in equality type. So rewrite rules are even powerful enough to experiment with replacements for core parts of Agda.

3 Type theory with user-defined rewrite rules

In the previous section, I gave several examples of how to use rewrite rules in Agda to make programming and proving easier and to experiment with new extensions to type theory. The next two sections go into the details of how rewrite rules work in general. Instead of starting with a complex language like Agda, I start with a small core language and gradually extend it by adding more features to the rewriting machinery step by step. In the next section, I will extend this language with other features that you are used to from Agda. The full rules of the language can be found in Appendix A.

3.1 Syntax We use a simplified version of the internal syntax used by Agda [22]. The syntax has five constructors: variables, function symbols, lambdas, pi types, and universes.

u, v, A, B ::= x u¯ (variable applied to zero or more arguments) | f u¯ (function symbol applied to zero or more arguments) | λx. u (lambda abstraction) | (x : A) → B (dependent function type) | Seti (ith universe) (1) As in the internal syntax of Agda, there is no way to represent a β-redex in this syntax. Instead, substitution uσ is defined to eagerly reduce β-redexes on the fly. Since terms are always in β-normal form, our rewrite system is a HRS (Higher-Order Rewrite system) in the spirit of Mayr and Nipkow [20]. Contexts are right-growing lists of variables annotated with their types.

Γ, ∆ ::= · (empty context) (2) | Γ(x : A) (context extension)

T Y P E S 2 0 1 9 2:12 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

Patterns p, q share their syntax with regular terms, but must satisfy some additional restrictions. To start with, the only allowed patterns are unapplied variables x and applica- tions of function symbols to other patterns f p¯. This allows us for example to declare rewrite rules like plus x zero −→ x and plus x (suc y) −→ suc (x + y).

3.2 Declarations There are two kinds of declarations: function symbols (corresponding to a postulate in Agda) and rewrite rules (corresponding to a postulate together with a {-# REWRITE #-} pragma). d ::= f : A (function symbol) (3) | ∀∆. f p¯ : A −→ v (rewrite rule) When the user declares a new rewrite rule, the following properties are checked: Linearity. Each variable in ∆ must occur exactly once in the pattern p¯ (this will later be relaxed to “at least once”). Well-typedness. The left- and right-hand side of the rewrite rule must be well-typed and have the same type, i.e. ∆ ` f p¯ : A and ∆ ` v : A. Neutrality. The left-hand side of the rewrite rule should be neutral, i.e. it should not reduce. The first restriction ensures that all variables of a rewrite rule are bound by the left-hand side. This ensures that reduction can never introduce variables that are not in scope, which would break well-scopedness of expressions. The second restriction ensures that applying a rewrite rule does not change the type of a well-typed expression.11 It is possible to go without the third restriction, but in practice this would mean that the rewrite rule would never be applied.12 Requiring rewrite rules to be well-typed has in some cases the unfortunate side-effect of introducing non-linearity where it is not really necessary, for example when defining the computation rule of the J eliminator as a rewrite rule. This non-linearity slows down the reduction unneccessarily and greatly complicates confluence checking. It would be interesting to investigate how to remove this unneccessary non-linearity, e.g. as proposed by Blanqui [8].

3.3 Reduction and matching To reduce a term f u¯, we look at the rewrite rules with head symbol f to see if any of them apply. In the rule below and all rules in the future, we assume a fixed global signature Σ containing all (preceding) declarations. (∀∆.f p¯ : A −→ v) ∈ Σ [¯u // p¯] ⇒ σ (4) f u¯ −→ vσ Matching a term u against a pattern p [u //p] ⇒ σ (or [¯u // p¯] ⇒ σ for matching a list of terms against a list of patterns) produces – if it succeeds – a substitution σ. In contrast to the first-match of clauses of a regular definition by pattern matching, all rewrite rules are considered in parallel, so there is no need for separate notion of a failing match.

11 To prove type preservation we also need confluence of reduction, see the future work section for more details. 12 If the rewrite system is globally confluent and strongly normalizing, it does not matter that we never apply a certain rewrite rule. Global confluence ensures that even if we apply a different rewrite rule, the result will still be the same, and strong normalization ensures that termination does not depend on the choice of rewrite rule either. Hence as a user one does not have to worry about the precise rewrite strategy implemented by Agda, but only about confluence and termination of the rewrite system. J. Cockx 2:13

u −→∗ f v¯ [¯v // p¯] ⇒ σ [u //x] ⇒ [u / x] [u // f p¯] ⇒ σ

[u //p] ⇒ σ1 [¯u // p¯] ⇒ σ2

[· // ·] ⇒ [] [u;u ¯ //p;p ¯] ⇒ σ1 ] σ2

Figure 1 Basic rules for the matching algorithm used for rewriting.

The basic matching algorithm is defined by the rules in Fig. 1. Matching a term against a pattern variable produces a substitution that assigns the given value to the variable. Matching an expression against a pattern f p¯ evaluates the expression until it becomes of the form f v¯ (here −→∗ is the reflexive and transitive closure of −→). It then recursively matches the arguments v¯ against the patterns p¯, combining the results of each match by taking the disjoint union σ1 ] σ2. Since matching can reduce the term being matched, matching and reduction are mutually recursive.

3.4 Higher-order matching With the basic set of rewrite rules introduced in the previous section, we can already declare a surprisingly large number of rewrite rules for first-order algebraic structures. From the examples in Sect. 2, it handles all of Sect. 2.1, rules map−fuse and map−++ from Sect. 2.2, all of Sect. 2.3, rule //−beta from Sect. 2.4, rules catch−true, catch−false, and catch−exc from Sect. 2.5, and the rules dealing with Bool in Sect. 2.6. Most of the examples that are not yet handled use λ and/or function types in the pattern of a rewrite rule. This brings us to the issue of higher-order matching.13 To support higher-order matching, we extend the pattern syntax with the following patterns:

A lambda pattern λx. p A function type pattern (x : p) → q A bound variable pattern y p¯ , where y is a variable bound locally in the pattern by a lambda or function type A pattern variable x y¯ applied to locally bound variables

During matching we must keep the (rigid) bound variables separate from the (flexible) pattern variables. For this purpose, the algorithm keeps a list Φ of all rigid variables. This list is not touched by any of the rules of Fig. 1, but any variables bound by a λ or a function type are added to it. The extended matching rules for higher-order patterns are given in Fig. 2. Note the strong similarity between the third rule and the rule for matching a function symbol f. This is not a coincidence: both function symbols and bound variables act as rigid symbols that can be matched against. The first three rules in Fig. 2 extend the pattern syntax to allow for bound variables in patterns, and allow for rules such as map − id : map (λ x → x) xs ≡ xs. However, alone they do not yet constitute true higher-order matching (such as used in rules raise−fun, cong−Π, and cong−λ). For this we also consider pattern variables applied to zero or more

13 See also https://github.com/agda/agda/issues/1563 for more examples where higher-order matching is needed.

T Y P E S 2 0 1 9 2:14 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

u −→∗ λx. v Φ, x ` [v //p] ⇒ σ Φ ` [u //λx. p] ⇒ σ

∗ A −→ (x : B) → C Φ ` [B //p] ⇒ σ1 Φ, x ` [C //q] ⇒ σ2

Φ ` [A // (x : p) → q] ⇒ σ1 ] σ2

u −→∗ x v¯ x ∈ ΦΦ ` [¯v // p¯] ⇒ σ x 6∈ Φ FV (v) ∩ Φ ⊆ y¯ Φ ` [u //x p¯] ⇒ σ Φ ` [v //x y¯] ⇒ [(λy.¯ v) / x]

Figure 2 Rules for higher-order pattern matching.

arguments. Allowing arbitrary patterns as arguments to pattern variables is well known to make matching undecidable, so we restrict patterns to Miller’s pattern fragment [21] by requiring pattern variables to be applied to distinct bound variables. Matching against a pattern variable in the Miller fragment is implemented by the fourth rule in Fig. 2. Since all the arguments of x are variables, we can construct the lambda term λy.¯ v. To avoid having out-of-scope variables in the resulting substitution, the free variables in v are checked to be included in y¯, otherwise matching fails.

3.5 Eta equality The attentive reader may have noticed a flaw in the matching for λ-patterns: it does not respect η-equality. With η-equality for functions, any term u :(x : A) → B x can always be expanded to λx. u x, so it should also match a pattern λx. p. A naive attempt to add η-equality would be to η-expand on the fly whenever we match something against a λ-pattern:

Φ, x ` [u x //p] ⇒ σ (5) Φ ` [u //λx. p] ⇒ σ This is however not enough to deal with η-equality in general. It is possible that the pattern itself is underapplied as well, e.g. when we match a term of type (x : A) → B x against a pattern f p¯ or x p¯. For example, when we have symbols f :(Nat → Nat) → Bool and g : Nat → Nat with rewrite rules fg −→ true and ∀(x : Nat). g x −→ x, then we want f (λx. x) to reduce to true, but with the above rule matching is stuck on the problem [λx. x // g]. To respect eta equality for functions and record types, we need to make matching type- directed. We also need contexts with the types of the free and bound variables. Thus we extend the matching judgement to Γ; Φ ` [u : A //p] ⇒ σ where A is the type of u (note: not necessarily the same as the type of p) and Γ and Φ are now contexts of pattern variables and bound variables respectively. The type information is used by the matching algorithm to do on-the-fly η-expansion of functions whenever the type is (or computes to) a function type:

A −→∗ (x : B) → C Γ; Φ(x : B) ` [u x : C //p x] ⇒ σ (6) Γ; Φ ` [u : A //p] ⇒ σ Here p x is only defined if the result is actually a pattern, otherwise the rule cannot be applied. J. Cockx 2:15

Having access to the type of the expression being matched is not only useful for η-equality of functions, but also for non-linear patterns (Sect. 3.6), η-equality for records (Sect. 4.1), and irrelevance (Sect. 4.4). While type-directed matching might slow down the reduction in some cases, I believe in many cases the benefits outmatch this disadvantage. Moreover, using irrelevance to avoid unneccessary conversion checks might even make up for the lost performance.

3.6 Non-linearity and conditional rewriting

Sometimes it is desirable to declare rewrite rules with non-linear patterns, i.e. where a pattern variable occurs more than once. As an example, this allows us to postulate an equality proof trustMe :(x y : A) → x ≡ y with a rewrite rule trustMe x x ≡ refl. This can be used in a similar way to Agda’s built-in primTrustMe14. Another example where non-linearity is used is the rule transportR−refl from the example in Sect. 2.4, which is non-linear in its two implicit arguments x and y.15 Non-linear matching is a specific instance of conditional rewriting. For example, the non-linear rule trustMe x x ≡ refl can be seen equivalently as the linear rule trustMe x y ≡ refl with an extra condition x = y : A. Using conditional rewriting, we can not only allow non-linear patterns but also patterns that contain arbitrary terms that do not fall in the pattern fragment. Like for non-linear rules, these “non-pattern” parts of the pattern are replaced by a fresh variable and a constraint that enforces this variable to be definitionally equal to the actual term. The only restriction is that all variables must be bound at least once in a pattern position. This use of conditional rewriting is similar to to inaccessible patterns (also known as dot patterns in Agda) used in dependent pattern matching, with the important difference that inaccessible patterns are guaranteed to match by the type system, while the constraints for conditional rewriting have to be checked. To check the equality constraints of conditional rewrite rules, the matching algorithm needs to decide whether two given terms are definitionally equal. This means reduction and matching are now mutually recursive with conversion checking.16 We make use of a type-directed conversion judgement Γ ` u = v : A (see the appendix for the full conversion rules). The new judgement form of matching is now Γ; Φ ` [v : A //p] ⇒ σ;Ψ, where Ψ is a set of constraints of the form Φ ` u =? v. We extend the matching algorithm with the ability to generate new constraints:

(7) Γ; Φ ` [v : A //p] ⇒ []; {Φ ` v =? p : A}

14 https://agda.readthedocs.io/en/v2.6.1/language/built-ins.html#primtrustme 15 It also needs irrelevance for Prop, see Sect. 4.4 for more details. 16 To actually change the implementation of Agda to make the matching depend on conversion checking took quite some effort (see https://github.com/agda/agda/pull/3589). The reason for this difficulty was that reduction and matching are running in one monad ReduceM, while conversion was running in another monad TCM (short for “type-checking monad”). The new version of the conversion checker is polymorphic in the monad it runs in. This means the same piece of code implements at the same time a pure, declarative conversion checker and a stateful constraint solver.

T Y P E S 2 0 1 9 2:16 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

All other rules just gather the set of constraints, taking the union whenever matching produces multiple sub-problems. When matching concludes, the constraints are checked before the rewrite rule is applied:

f :Γ → A ∈ Σ(∀∆.f p¯ : B −→ v) ∈ Σ [¯u : Γ[¯u] // p¯] ⇒ σ;Ψ ∀(Φ ` v =? p : A) ∈ Ψ. Φ ` v = pσ : A (8) f u¯ −→ vσ

When checking a constraint we apply the final substitution σ to the pattern p but not to the term v or the type A. This makes sense because the term being matched does not contain any pattern variables in the first place (and neither does its type).

4 Interaction with other features

Adding rewrite rules to an existing language such as Agda is quite an undertaking. Re- write rules often interact with other features in a non-trivial matter, and it takes work to resolve these interactions in a satisfactory way. In this section, I describe the interaction of rewrite rules with several other features of Agda: record types with eta equality, data- types, parametrized modules, definitional irrelevance, universe polymorphism, and constraint solving.

4.1 Eta equality for records

Agda has η-equality not just for function types, but also for record types. For example, any term u : A × B is definitionally equal to (fst u, snd u). Since η-equality of records is a core part of Agda, we extend the matching algorithm to deal with it.17 As for η-equality of functions, we make use of the type of the expression to η-expand terms and patterns during matching.

Let R : Seti be a record type with fields π1 : A1, ..., πn : An. We have the following matching rule:

j

Since records can be dependent, each type Ai may depend on the previous fields π1,...,π i−1, so we need to substitute the concrete values πj u for πj in Ai for each j < i. In the case where n = 0, this rule says that a term of the unit record type > (with no fields) matches any pattern. So the matching algorithm even handles the notorious η-unit types.

4.2 Datatypes and constructors

An important question is how rewrite rules interact with datatypes such as Nat, List, and _≡_. Can we simply add rewrite rules to (type and/or term) constructors? The answer is actually a bit more complicated.

17 See https://github.com/agda/agda/issues/2979 and https://github.com/agda/agda/issues/ 3335. J. Cockx 2:17

If we allow rewriting of datatype constructors, we could (for example) postulate an equality proof of type Nat ≡ Bool and register it as a rewrite rule. However, this would mean zero : Bool, violating an important internal invariant of Agda that any time we have c u¯ : D for a constructor c and a datatype D, c is actually a constructor of D.18 For this reason, it is not allowed to have rewrite rules on datatypes or record types. For constructors of datatypes there is no a priori reason why they cannot have rewrite rules attached to them. This would actually be useful to define a “definitional quotient type” where some of the constructors may compute. Unfortunately, there is another problem: internally, Agda does not store the constructor arguments corresponding to the parameters of the datatype. For example, the constructors [] and _::_ of the List A type do not store the type A as an argument. This is important for efficient representation of parametrized datatypes. However, this means that rewrite rules that match on constructors cannot match against arguments in those positions, or bind pattern variables in them. When a rewrite rule is added with a constructor as the head symbol, we have to take care that the rewrite rule is not applied too generally. For example, a rewrite rule for [] : List Nat should not be applied to [] : List A where A 6= Nat19. To avoid unwanted reductions like these, it is only allowed to add a rewrite rule to a constructor if the parameters are fully general, i.e. they must be distinct variables. This ensures that rewrite rules are only applied to terms whose type matches the type of the rewrite rule.

4.3 Parametrized modules and “where” blocks

A parametrized module is a collection of declarations parametrized over a common telescope Γ. In one sense, parametrized modules can be thought of as λ-lifting all the definitions inside the module: if a module with parameters Γ contains a definition of f : A, then the real type of f is Γ → A. But this does not quite capture the intuition that definitions inside a parametrized module should be parametric in the parameters. So module parameters should be treated as rigid symbols like postulates rather than as flexible variables. For this reason, module parameters play a double role on the left-hand side of a rewrite rule: As long as the parameter is in scope (i.e. inside the module), it has to match “on the nose” (i.e. it cannot be instantiated by matching). Once the parameter goes out of scope (i.e. outside of the module), it is treated as a regular pattern variable that can be instantiated by matching. For example, inside a module parametrized over n : Nat, a rewrite rule f n −→ zero only applies to terms definitionally equal to f n. On the other hand, outside of the module the rewrite rule applies to any expression of the form f u. This intuition of module parameters as rigid symbols also applies to Agda’s treatment of where blocks, which are nothing more than modules parametrized over the pattern variables of the clause (you can even give a name to the where module using the moduleM where syntax20). Here a rewrite rule declared in a where block should only apply for the specific arguments to the function that are used in the clause, not those of a recursive call21.

18 See https://github.com/agda/agda/issues/3846. 19 See https://github.com/agda/agda/issues/3211. 20 https://agda.readthedocs.io/en/v2.6.1/language/let-and-where.html#where-blocks 21 https://github.com/agda/agda/issues/1652

T Y P E S 2 0 1 9 2:18 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

4.4 Irrelevance and Prop Another feature of Agda is definitional irrelevance, which comes in the two flavours of irrelevant function types .A → B22 and the universe Prop of definitionally proof-irrelevant propositions23. For rewrite rules with irrelevant parts in their patterns matching should never fail because this would mean a supposedly irrelevant term is not actually irrelevant. However, it should still be allowed to bind a variable in an irrelevant position, since we might want to use that variable in (irrelevant positions of) the right-hand side.24 This means in irrelevant positions we allow: 1. pattern variables x y¯ where y¯ are all the bound variables in scope, and 2. arbitrary terms u that do not bind any variables.

Both of these will always match any given term: the former because y¯ is required to consist of all bound variables, and the latter because two irrelevant terms are always considered equal by the conversion checker. However, only the former can bind a variable. Together with the ability to have non-linear patterns, this allows us to have rewrite rules such as transportR − refl : transportR P refl x ≡ x where transportR :(P : A → Set ) → . . ` x = y → P x → P y and x = y is the equality type in Prop. The constructor refl here is irrelevant, so this rule does not actually match against the constructor refl. Instead, Agda checks that the two arguments x and y are definitionally equal, and applies the rewrite rule if this is the case.

4.5 Universe level polymorphism Universe level polymorphism allows Agda programmers to write definitions that are poly- morphic in the universe level of a type parameter. Since the type Level of universe levels is a first-class type in Agda, it interacts natively with rewrite rules: patterns can bind variables of type Level just as any other type. This allows us for example to define rewrite rules such as map − id that work on level-polymorphic lists. The type Level supports two operations lsuc : Level → Level and _ t _ : Level → Level → Level. These operations have a complex equational structure: _t_ is associative, commutative, and idempotent, and lsuc distributes over _t_, just to name a few of the laws. This causes trouble when a rewrite rule matches against one of these symbols: how should it determine whether a given level matches a t b when _t_ is commutative?25 For this reason it is not allowed to have rewrite rules that match against lsuc or_ t_. This restriction on patterns of type Level seems reasonable enough, but it is often not satisfied by rewrite rules that match on function types – like the cong−Π rule we used in

the encoding of observational type theory (Sect. 2.6). The problem is that if A : Set`1 and

B : Set`2 , then the function type (x : A) → B has type Set`1 t `2 , so there is no sensible position to bind the variables `1 and `2. To allow rewrite rules such as cong−Π, we need to find a different position where these variables of type Level can be bound. In the internal syntax of Agda, function types (x : A) → B are annotated with the sorts of A and B. So the “real” function type of Agda

22 https://agda.readthedocs.io/en/v2.6.1/language/irrelevance.html 23 https://agda.readthedocs.io/en/v2.6.1/language/prop.html 24 See https://github.com/agda/agda/issues/2300. 25 Issue #2090 (https://github.com/agda/agda/issues/2090) and issue #2299 (https://github.com/ agda/agda/issues/2299) show some of the things that would go wrong. J. Cockx 2:19

is of the form (x : A : Set`1 ) → (B : Set`2 ). This means that if we allow rewrite rules to bind pattern variables in these hidden annotations, we are saved.26 The matching rule for function types now becomes:

Γ; Φ ` [A : Set `1 //p] ⇒ σ1;Ψ1 Γ; Φ ` [`1 : Level //q] ⇒ σ2;Ψ2 Γ; Φ(x : A) ` [B : Set `2 //r] ⇒ σ3;Ψ3 Γ; Φ ` [`2 : Level //s] ⇒ σ4;Ψ4 (10) Γ; Φ ` [(x : A : Set `1) → (B : Set `2) // (x : p : Setq) → (r : Sets)] ⇒ (σ1 ] σ2 ] σ3 ] σ4); (Ψ1 ∪ Ψ2 ∪ Ψ3 ∪ Ψ4)

Thanks to this rule, also the universe-polymorphic version of the rewrite rules in Sect. 2.6 are accepted by Agda.

4.6 Metavariables and constraint solving To automatically fill in the values of implicit arguments, Agda inserts metavariables as their placeholders. These metavariables are then solved during typechecking by the constraint solver. A full description of Agda’s constraint solver is out of the scope of this paper, but let me discuss the most important ways it is impacted by rewrite rules.

4.6.1 Blocking tags The constraint solver needs to know when a reduction is blocked on a particular metavariable. Usually it is possible to point out a single metavariable, but this is no longer the case when rewrite rules are involved: With overlapping rewrite rules, reduction can be blocked on a set of metavariables. For example, if we try to reduce the expression X + Y where X and Y are metavariables of type Nat and_ +_ is defined with the rewrite rules from Sect. 2.1, then this expression might reduce further when either X or Y is instantiated to a constructor. So a postponed constraint involving this expression has to be woken up when either metavariable is instantiated. For higher-order matching, matching checks whether a particular variable occurs freely in the body of a lambda or pi. When metavariables are involved, a variable occurrence may be flexible: whether or not the variable occurs depends on the instantiation of a particular metavariable27. In this case reduction is blocked on the set of all metavariables with potentially unbound variables in their arguments. When a conditional rewrite rule is blocked on the conversion check because of an unsolved metavariable, reduction can be blocked on the metavariable that is preventing the conversion check from succeeding.2829

Currently the Agda implementation uses only an approximation of the set of metavariables it encounters, i.e. only the first metavariable encountered. This is harmless because the current implementation of Agda will eventually try again to solve all postponed constraints. If in the future Agda would be changed to be more careful in when it decided to wake up postponed constraints, a more precise tracking of blocking metavariables would also be desirable.

26 See also https://github.com/agda/agda/issues/3971. 27 https://github.com/agda/agda/issues/1663 28 https://github.com/agda/agda/issues/1987 29 https://github.com/agda/agda/issues/2302

T Y P E S 2 0 1 9 2:20 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

4.6.2 Pruning and constructor-like symbols When adding new rewrite rules, we also keep track of what symbols are constructor-like. This is important for the pruning phase of the constraint solver. For example, let us consider a constraint X =? Y (f x). Since the metavariable X does not depend on the variable x, the constraint solver attempts to prune the dependency of Y on x. If f is a regular postulate without any rewrite rules, there is no way that Y could depend on f x without also depending on x, so the dependency of Y on its first argument is pruned away. However, if there is a rewrite rule where f plays the role of a constructor – say a rule g (f y) −→ true – then the assignment X := true and Y := λy. g y is a valid solution to the constraint where Y does depend on its argument, so it should not be pruned away. In general, an argument should not be pruned if the head symbol is constructor-like, i.e. if there is at least one rewrite rule that matches against the symbol.

5 Related work

The idea of extending dependent type theory with rewrite rules is not new and has been studied from many different angles. The groundwork of all this work was laid in 1988 by Breazu-Tannen [29], who extended simply typed lambda calculus with first-order and higher- order rewrite rules (but not higher-order matching). In what follows, I give an overview of some important milestones, focussing on languages that combine dependent types and higher-order rewrite rules. The idea of extending dependent type theory with rewrite rules originates in the work by Barbanera, Fernandez, and Geuvers [5]. They present the algebraic λ-cube, an extension of the λ-cube with algebraic rewrite rules, and study conditions for strong normalization. Since the left-hand sides of rewrite rules must be algebraic terms, this work does not include higher-order matching. Several lines of work investigate possible ways to integrate rewrite rules into the Calculus of Constructions, with or without inductive datatypes: Walukiewicz-Chrz¸aszcz[31] extends the calculus of constructions with inductive types and rewrite rules, and gives a termination criterion based on HORPO (higher-order recursive path ordering). Later, Walukiewicz-Chrz¸aszczand Chrz¸aszczalso discuss the question of completeness and consistency of this system [32], and consider the addition of rewrite rules to the Coq proof assistant [12]. The Open Calculus of Constructions [26, 27] integrates features from the Calculus of Constructions (CoC) with conditional rewrite rules, as well as other kinds of equational reasoning. It provides many of the same benefits as our system and is even more powerful when it comes to conditional rewrite rules. However it again does not provide higher-order matching or η-equality. It has a prototype implementation using the Maude language [13]. The Calculus of Algebraic Constructions (CAC) [7] is another extension of the Calculus of Constructions with functions and predicates defined by higher-order rewrite rules. Compared to our implementation of rewrite rules, CAC is more limited in that it does not allow higher-order matching, but it provides criteria for checking subject reduction and strong normalization of the rewrite rules.

Coq modulo theory (CoqMT) [28] and the newer version CoqMTU [6, 18] extend the Coq proof assistant with a decidable theory. The equational theory in CoqMTU must be first-order, but can include equational rules such as commutativity, which cannot be expressed J. Cockx 2:21 as rewrite rules. CoqMTU also provides strong guarantees for confluence, subject reduction, strong normalization, and consistency of the theory. Unfortunately, the implementation of CoqMTU30 has not been updated to work with the current version of Coq. Our extension of dependent type theory with rewrite rules resembles in many ways the Dedukti system [15, 24, 10, 4]. Both systems support dependent types and higher-order non-linear rewrite rules. There are however some important differences: Dedukti was built up from the ground based on rewrite rules. In contrast, we start from a general dependently typed language (Agda) and extend it with rewrite rules. Dedukti is based on the Logical Framework (LF) [17], while our language is build from Martin-Löf’s intuitionistic type theory [19], which includes several features not present in LF such as sigma types, W-types, identity types, and a universe hierarchy. Dedukti has universes à la Tarski: a universe is a set of codes that can be interpreted as types by an function. In contrast, Agda uses universes à la Russell: elements of a universe are types without need of an interpretation function. Dedukti uses an untyped conversion algorithm, while Agda uses a typed one. Hence we can support η-equality for functions and record types, which is not possible (directly) in Dedukti. Dedukti provides external tools for checking confluence and termination of the rewrite system given by the user. Applying the same strategy to rewrite rules in Agda would be difficult because several features cannot be translated into standard rewrite systems, e.g. copattern matching, eta-equality, irrelevance, and universe levels. All of these features introduce additional definitional equalities that should be taken into account when computing critical pairs. A confluence checker that does not would not detect all critical pairs and thus only be of limited use. Instead, we are currently working on integrating a confluence checker into Agda directly.31

The Zombie language [25] is another dependently typed language where definitional equality can be extended with user-provided equations that are applied automatically by the typechecker. Instead of rewrite rules, Zombie computes the congruence closure of the given equations and uses this during conversion checking. An important difference with our approach is that the definitional equality in Zombie does not include β-equality, which makes it easier to extend it in other directions. The congruence closure algorithm used by Zombie is untyped, which means it cannot handle η-equality of functions or records. It also does not include higher-order matching. Our treatment of rewrite rules in parametrized modules is very similar to the one given by Chrz¸aszcz[11]. The main difference is that Chrz¸aszczconsiders modules parametrized by other modules, while in Agda modules are parametrized by term variables. So our system is a bit simpler since we cannot have rewrite rules as parameters.

6 Future work

Safe(r) rewrite rules This paper is about how to add rewrite rules to Agda or similar languages. By their design rewrite rules are a very unsafe feature of Agda. Compared to using postulate, rewrite rules by themselves do not break logical soundness of the theory, since it can only be used to turn

30 https://github.com/strub/coqmt 31 The development version of Agda already includes an experimental flag --confluence-check, checking local confluence of rewrite rules.

T Y P E S 2 0 1 9 2:22 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

propositional equalities into definitional ones. Logical consistency of the system thus follows from the consistency of extensional type theory. While several of our examples do introduce equalities that were previously unprovable (specifically higher inductive types, quotient types, exceptions, and observational equality), they do so by first explicitly postulating the required propositional equality and then registering it as a rewrite rule. Hence as long as the postulates preserve logical soundness, we can trust that turning them into rewrite rules does not break soundness either. On the other hand, rewrite rules can break core assumptions of Agda such as confluence of reduction and even type preservation. So using rewrite rules is like building your own type theory, which means you have to do your own meta-theory to make sure everything is safe. Ideally, Agda would be able to detect if a given set of rewrite rules is “safe”, in the sense that they do not break the usual properties of Agda programs such as subject reduction and decidable typechecking. The development version of Agda 2.6.1 includes an experimental flag --confluence-check, which checks the local confluence of the declared rewrite rules. We are currently working to improve this confluence checker to also enforce global confluence of the rewrite rules. This would allow us to prove injectivity of Π types, and hence subject reduction of our type theory. For checking termination – and hence decidability of typechecking – we could make use of the dependency pairs criterion as done by SizeChangeTool for Dedukti [9].

Local rewrite rules

When programming in a dependently typed language, we rely on terms computing to their values. However, this fails when we work with abstract values (e.g. module parameters): until they are instantiated, they are opaque symbols without any computational behaviour. This actively encourages users to work with concrete values and discourages abstraction. To improve this situation, we could allow local rewrite rules on module parameters to be added to the context. For example, we could parametrize a module over a value ∅ and a _ · _ together with rewrite rules ∅· y −→ y and x · ∅ −→ x. When instantiating the module parameters, we have to check that that the given instantiation of the parameters satisfies each of the rewrite rules as a definitional equality. Having local rewrite rules greatly complicates checking of confluence and termination. So the future will have to point out if there is a reasonable way to allow local rewrite rules while maintaining subject reduction of the language.

Custom η rules

Rewrite rules allow us to add custom β rules to our type theory, but it would be useful to also allow custom η rules. This would for example allow us to add η-rules for datatypes such as Vec, making any vector of length zero definitionally equal to []. Where rewrite rules allow extending the reduction relation of the theory, custom η rules would allow extending the conversion checker directly. Since conversion in Agda is type-directed, it would make sense to allow custom η rules that match against the type of a constraint. Thus much of the matching algorithm in this paper could be reused for η rules.

7 Conclusion

This paper documents the process of integrating user-defined rewrite rules into a general- purpose dependently typed language, and all the weird interactions that I encountered along the way. Rewrite rules allow you to extend the power of a dependently typed language on a J. Cockx 2:23 much deeper level than normally allowed. They can be used as a convenient feature to make more terms typecheck without using explicit rewrite statements, and they allow advanced users to experiment with new evaluation rules, without actually modifying the typechecker. If you are an Agda user, I hope reading this paper has given you a deeper understanding of rewrite rules and allows you to harness their power responsibly. And if you are implementing your own dependently typed language, I hope you consider adding rewrite rules as a way to make it both easier to use and more extensible.

References 1 Agda development team. Agda 2.6.1 documentation, 2020. URL: http://agda.readthedocs. io/en/v2.6.1/. 2 Guillaume Allais, Conor McBride, and Pierre Boutillier. New equations for neutral terms: a sound and complete decision procedure, formalized. In Stephanie Weirich, editor, Proceedings of the 2013 ACM SIGPLAN workshop on Dependently-typed programming, DTP@ICFP 2013, Boston, Massachusetts, USA, September 24, 2013, pages 13–24. ACM, 2013. doi: 10.1145/2502409.2502411. 3 Thorsten Altenkirch, Conor McBride, and Wouter Swierstra. Observational equality, now! In Proceedings of the 2007 workshop on Programming languages meets program verification, pages 57–68. ACM, 2007. 4 Ali Assaf and Guillaume Burel. Translating HOL to dedukti. In Cezary Kaliszyk and Andrei Paskevich, editors, Proceedings Fourth Workshop on Proof eXchange for Proving, PxTP 2015, Berlin, Germany, August 2-3, 2015., volume 186 of EPTCS, pages 74–88, 2015. doi:10.4204/EPTCS.186.8. 5 Franco Barbanera, Maribel Fernández, and Herman Geuvers. Modularity of strong normal- ization in the algebraic-lambda-cube. Journal of Functional Programming, 7(6):613–660, 1997. 6 Bruno Barras, Jean-Pierre Jouannaud, Pierre-Yves Strub, and Qian Wang. CoQMTU: A higher-order type theory with a predicative hierarchy of universes parametrized by a decidable first-order theory. In Proceedings of the 26th Annual IEEE Symposium on Logic in Computer Science, LICS 2011, June 21-24, 2011, Toronto, Ontario, Canada, pages 143–151. IEEE Computer Society, 2011. doi:10.1109/LICS.2011.37. 7 Frédéric Blanqui. Definitions by rewriting in the calculus of constructions. Mathematical Structures in Computer Science, 15(1):37–92, 2005. doi:10.1017/S0960129504004426. 8 Frédéric Blanqui. Type safety of rewriting rules in dependent types. In At the 26th Internationan Conference on Types for Proofs and Programs (TYPES 2020), 2020. 9 Frédéric Blanqui, Guillaume Genestier, and Olivier Hermant. Dependency pairs termination in dependent type theory modulo rewriting. In Herman Geuvers, editor, 4th International Conference on Formal Structures for Computation and Deduction, FSCD 2019, June 24- 30, 2019, Dortmund, Germany., volume 131 of LIPIcs, pages 9:1–9:21. Schloss Dagstuhl - Leibniz-Zentrum fuer Informatik, 2019. doi:10.4230/LIPIcs.FSCD.2019.9. 10 Mathieu Boespflug, Quentin Carbonneaux, and Olivier Hermant. The λπ-calculus modulo as a universal proof language. In the Second International Workshop on Proof Exchange for Theorem Proving (PxTP 2012), 2012. 11 Jacek Chrzaszcz. Modules in Coq are and will be correct. In Stefano Berardi, Mario Coppo, and Ferruccio Damiani, editors, Types for Proofs and Programs, International Workshop, TYPES 2003, Torino, Italy, April 30 - May 4, 2003, Revised Selected Pa- pers, volume 3085 of Lecture Notes in Computer Science, pages 130–146. Springer, 2003. URL: http://springerlink.metapress.com/openurl.asp?genre=article&issn= 0302-9743&volume=3085&spage=130.

T Y P E S 2 0 1 9 2:24 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

12 Jacek Chrzaszcz and Daria Walukiewicz-Chrzaszcz. Towards rewriting in Coq. In Hubert Comon-Lundh, Claude Kirchner, and Hélène Kirchner, editors, Rewriting, Computation and Proof, Essays Dedicated to Jean-Pierre Jouannaud on the Occasion of His 60th Birthday, volume 4600 of Lecture Notes in Computer Science, pages 113–131. Springer, 2007. doi: 10.1007/978-3-540-73147-4_6. 13 Manuel Clavel, Francisco Durán, Steven Eker, Patrick Lincoln, Narciso Martí-Oliet, José Meseguer, and Jose F. Quesada. Maude: specification and programming in rewriting logic. Theoretical Computer Science, 285(2):187–243, 2002. doi:10.1016/S0304-3975(01)00359-0. 14 Jesper Cockx, Frank Piessens, and Dominique Devriese. Overlapping and order-independent patterns - definitional equality for all. In Zhong Shao, editor, Programming Languages and Systems - 23rd European Symposium on Programming, ESOP 2014, Held as Part of the European Joint Conferences on Theory and Practice of Software, ETAPS 2014, Grenoble, France, April 5-13, 2014, Proceedings, volume 8410 of Lecture Notes in Computer Science, pages 87–106. Springer, 2014. doi:10.1007/978-3-642-54833-8_6. 15 Denis Cousineau and Gilles Dowek. Embedding pure type systems in the lambda-pi-calculus modulo. In Simona Ronchi Della Rocca, editor, Typed Lambda Calculi and Applications, 8th International Conference, TLCA 2007, Paris, France, June 26-28, 2007, Proceedings, volume 4583 of Lecture Notes in Computer Science, pages 102–117. Springer, 2007. doi: 10.1007/978-3-540-73228-0_9. 16 Gaëtan Gilbert, Jesper Cockx, Matthieu Sozeau, and Nicolas Tabareau. Definitional proof- irrelevance without K. PACMPL, 3(POPL):3:1–3:28, 2019. doi:10.1145/3290316. 17 Robert Harper, Furio Honsell, and Gordon D. Plotkin. A framework for defining logics. Journal of the ACM, 40(1):143–184, 1993. doi:10.1145/138027.138060. 18 Jean-Pierre Jouannaud and Pierre-Yves Strub. Coq without type casts: A complete proof of Coq modulo theory. In Thomas Eiter and David Sands, editors, LPAR-21, 21st International Conference on Logic for Programming, Artificial Intelligence and Reasoning, Maun, Botswana, May 7-12, 2017, volume 46 of EPiC Series in Computing, pages 474–489. EasyChair, 2017. URL: http://www.easychair.org/publications/paper/340342. 19 Per Martin-Löf. Intuitionistic type theory, volume 1 of Studies in . Bibliopolis, 1984. 20 Richard Mayr and Tobias Nipkow. Higher-order rewrite systems and their confluence. Theor- etical Computer Science, 192(1):3–29, 1998. doi:10.1016/S0304-3975(97)00143-6. 21 Dale Miller. A logic programming language with lambda-abstraction, function variables, and simple unification. J. Log. Comput., 1(4):497–536, 1991. doi:10.1093/logcom/1.4.497. 22 Ulf Norell. Towards a practical programming language based on dependent type theory. PhD thesis, Department of Computer Science and Engineering, Chalmers University of Technology, SE-412 96 Göteborg, Sweden, September 2007. 23 Pierre-Marie Pédrot and Nicolas Tabareau. Failure is not an option - an exceptional type theory. In Amal Ahmed, editor, Programming Languages and Systems - 27th European Symposium on Programming, ESOP 2018, Held as Part of the European Joint Conferences on Theory and Practice of Software, ETAPS 2018, Thessaloniki, Greece, April 14-20, 2018, Proceedings, Lecture Notes in Computer Science, pages 245–271. Springer, 2018. doi:10.1007/ 978-3-319-89884-1_9. 24 Ronan Saillard. Typechecking in the lambda-Pi-Calculus Modulo: Theory and Practice. PhD thesis, MINES ParisTech, 2015. 25 Vilhelm Sjöberg and Stephanie Weirich. Programming up to congruence. In Sriram K. Rajamani and David Walker, editors, Proceedings of the 42nd Annual ACM SIGPLAN- SIGACT Symposium on Principles of Programming Languages, POPL 2015, Mumbai, India, January 15-17, 2015, pages 369–382. ACM, 2015. doi:10.1145/2676726.2676974. 26 Mark-Oliver Stehr. The open calculus of constructions (part i): An equational type theory with dependent types for programming, specification, and interactive theorem proving. Fundamenta Informaticae, 68(1-2):131–174, 2005. J. Cockx 2:25

27 Mark-Oliver Stehr. The open calculus of constructions (part ii): An equational type theory with dependent types for programming, specification, and interactive theorem proving. Fundamenta Informaticae, 68(3):249–288, 2005. 28 Pierre-Yves Strub. Coq Modulo Theory. In Anuj Dawar and Helmut Veith, editors, Computer Science Logic, 24th International Workshop, CSL 2010, 19th Annual Conference of the EACSL, Brno, Czech Republic, August 23-27, 2010. Proceedings, volume 6247 of Lecture Notes in Computer Science, pages 529–543. Springer, 2010. doi:10.1007/978-3-642-15205-4_40. 29 Val Tannen. Combining algebra and higher-order types. In Proceedings, Third Annual Symposium on Logic in Computer Science, 5-8 July 1988, Edinburgh, Scotland, UK, pages 82–90. IEEE Computer Society, 1988. 30 The Univalent Foundations Program. Homotopy Type Theory - Univalent Foundations of Mathematics: The Univalent Foundations Program. Institute for Advanced Study, 2013. URL: https://homotopytypetheory.org/book/. 31 Daria Walukiewicz-Chrzaszcz. Termination of rewriting in the calculus of constructions. Journal of Functional Programming, 13(2):339–414, 2003. doi:10.1017/S0956796802004641. 32 Daria Walukiewicz-Chrzaszcz and Jacek Chrzaszcz. Consistency and completeness of rewriting in the calculus of constructions. In Ulrich Furbach and Natarajan Shankar, editors, Automated Reasoning, Third International Joint Conference, IJCAR 2006, Seattle, WA, USA, August 17-20, 2006, Proceedings, volume 4130 of Lecture Notes in Computer Science, pages 619–631. Springer, 2006. doi:10.1007/11814771_50.

A Complete rules of type theory with user-defined rewrite rules

A.1 Syntax

Terms. u, v, w, A, B, C, p, q

u, v, w, A, B, C, p, q ::= x u¯ (variable applied to zero or more arguments) | f u¯ (function symbol applied to zero or more arguments) | λx. u (lambda abstraction) | (x : A) → B (dependent function type) | Seti (ith universe)

Substitutions. Substitutions σ are lists of variable-term pairs [u1 / x1, . . . , un / xn]. Ap- plication of a substitution to a term uσ is defined as usual, avoiding variable capture by α-renaming where necessary.

Application. Application u v is a partial operation on terms and is defined as follows:

(x u¯) v = x (¯u; v) (f u¯) v = f (¯u; v) (λx. u) v = u[v / x]

Contexts. Γ, ∆, Φ, Ξ

Γ, ∆, Φ, Ξ ::= · (empty context) | Γ(x : A) (context extension)

T Y P E S 2 0 1 9 2:26 Type Theory Unchained: Extending Agda with User-Defined Rewrite Rules

Declarations. d d ::= f : A (function symbol) | ∀∆. f p¯ : A −→ v (rewrite rule)

A.2 Typing rules We assume a global signature Σ containing declarations and rewrite rules, which is implicit in all the judgements.

Typing. Γ ` u : A x : A ∈ Γ f : A ∈ Σ Γ(x : A) ` u : B Γ ` u :(x : A) → B Γ ` v : A Γ ` x : A Γ ` f : A Γ ` λx. u :(x : A) → B Γ ` u v : B[v / x]

Γ ` A : Seti Γ(x : A) ` B : Setj Γ ` A = B : Seti Γ ` u : A

Γ ` (x : A) → B : Setitj Seti : Set1+i Γ ` u : B

Conversion. Γ ` u = v : A Γ ` u −→ u0 Γ ` u0 = v : A Γ ` v −→ v0 Γ ` u = v0 : A x : A ∈ Γ Γ ` u = v : A Γ ` u = v : A Γ ` x = x : A

f : A ∈ Σ Γ(x : A) ` u x = v x : B Γ ` u1 = u2 :(x : A) → B Γ ` v1 = v2 : A

Γ ` f = f : A Γ ` u = v :(x : A) → B Γ ` u1 v1 = u2 v2 : B[v1 / x]

Γ ` A1 = A2 : Seti Γ(x : A1) ` B1 = B2 : Setj

Γ ` (x : A1) → B1 = (x : A2) → B2 : Setitj

Reduction. Γ ` u −→ v f : B ∈ Σ(∀Ξ.f p¯ : C −→ v) ∈ Σ ΓΞ; · ` [(• : B)u ¯ // p¯] ⇒ σ;Ψ ∀(Φ ` v =? p : A) ∈ Ψ. ΓΦ ` v = pσ : A Γ ` f u¯ −→ vσ

Matching. Γ; Φ ` [u : A //p] ⇒ σ;Ψ

x : B ∈ Γ Γ; Φ ` [u : A //x] ⇒ [u / x]; ∅ Γ; Φ ` [u : A //v] ⇒ []; {Φ ` u =? v : A}

ΓΦ ` u −→∗ f v¯ f : B ∈ Σ Γ; Φ ` [(• : B)v ¯ // p¯] ⇒ σ;Ψ Γ; Φ ` [u : A // f p¯] ⇒ σ;Ψ

ΓΦ ` u −→∗ x v¯ x : B ∈ Φ Γ; Φ ` [(• : B)v ¯ // p¯] ⇒ σ;Ψ Γ; Φ ` [u : A //x p¯] ⇒ σ;Ψ

ΓΦ ` A −→∗ (x : B) → C Γ; Φ(x : B) ` [u x : C //p x] ⇒ σ;Ψ Γ; Φ ` [u : A //p] ⇒ σ;Ψ

∗ ΓΦ ` A −→ (x : B) → C Γ; Φ ` [B //p] ⇒ σ1;Ψ1 Γ; Φ(x : B) ` [C //q] ⇒ σ2;Ψ2

Γ; Φ ` [A : D // (x : p) → q] ⇒ σ1 ] σ2;Ψ1 ∪ Ψ2 J. Cockx 2:27

Spine matching. Γ; Φ ` [(• : A)u ¯ // p¯] ⇒ σ;Ψ

∗ ΓΦ ` A −→ (x : B) → C Γ; Φ ` [u : B //p] ⇒ σ1;Ψ1 Γ; Φ ` [(• : C[u / x])u ¯ // p¯] ⇒ σ2;Ψ2

Γ; Φ ` [(• : A) · // ·] ⇒ []; ∅ Γ; Φ ` [(• : A) u;u ¯ //p;p ¯] ⇒ σ1 ] σ2;Ψ1 ∪ Ψ2

A.3 Checking declarations

A declaration of a function symbol f : A is valid if Γ ` A : Seti. A declaration of a rewrite rule ∀∆. f p¯ : A −→ v is valid if: Each variable in ∆ occurs at least once in a pattern position in p¯. ∆ ` f p : A and ∆ ` v : A There is no term w such that ∆ ` f p¯ −→ w.

T Y P E S 2 0 1 9