How to tame your rewrite rules

Talk at AIM XXIX

Jesper Cockx

16 March 2019

Rewrite rules in Agda

Rewrite rules in Agda

{-# OPTIONS --rewriting #-}

open import Prelude.Equality
{-# BUILTIN REWRITE _≡_ #-}

A first example

open import Agda.Builtin.Nat
variable m n : Nat

module Fails where
  +-comm : m + n  n + m
  +-comm {zero}  = {!refl!}
              -- ^ n != n + zero
  +-comm {suc m} = {!cong suc (+-comm {m})!}
              -- ^ suc (m + n) != n + suc m

Fixing the problem with rewrite rules

postulate
  +0 : m + 0  m
  +suc : m + (suc n)  suc (m + n)
{-# REWRITE +0   #-}
{-# REWRITE +suc #-}

+-comm : m + n  n + m
+-comm {zero}  = refl
            -- ^ n != n + zero
+-comm {suc m} = cong suc (+-comm {m})
            -- ^ suc (m + n) != n + suc m

Higher inductive types

postulate
  I : Set
  i0 i1 : I
  seg : i0  i1

module _ (P : I  Set) (p0 : P i0) (p1 : P i1)
         (pseg : transport P seg p0  p1) where

  postulate
    elim-I   : (i : I)  P i
    elim-i0  : elim-I i0  p0
    elim-i1  : elim-I i1  p1
    elim-seg-fail : cong′ elim-I seg  {!pseg!}
          -- ^ p0 != elim-I i0

Rewrite rules to the rescue!

  {-# REWRITE elim-i0 #-}
  {-# REWRITE elim-i1 #-}
  postulate
    elim-seg : cong′ elim-I seg  pseg

Exceptional type theory

postulate raise : {A : Set}  A

module _ (P : Bool  Set)
         (ptrue : P true) (pfalse : P false)
         (handle : P raise) where
  postulate
    catch : (b : Bool)  P b
    catch-true : catch true  ptrue
    catch-false : catch false  pfalse
    catch-raise : catch raise  handle
{-# REWRITE catch-true  #-}
{-# REWRITE catch-false #-}
{-# REWRITE catch-raise #-}

Problems with rewrite rules

Rewrite rules are dangerous!

  • Non-termination: loop ⟶ loop
  • Non-confluence: b ⟶ true, b ⟶ false
  • Loss of subject reduction (!)

Main objective: automatically check safety of user-defined rewrite rules

Non-confluence ⇒ no subject reduction:

data Unit : Set where unit : Unit

postulate
  A : Unit  Set
  A-is-Bool : A unit  Bool
{-# REWRITE A-is-Bool #-}

f : (x : Unit)  A x
f unit = true

postulate A-is-Nat  :  {x}  A x  Nat
{-# REWRITE A-is-Nat #-}

true' : Nat
true' = f unit -- reduces to true!

Confluence ⇒ still no subject reduction:

record Box (A : Set) : Set where
  constructor box
  field unbox : A
open Box

postulate rew : Box (Nat  Nat)  Box (Nat  Bool)
{-# REWRITE rew #-}

zero' : Bool  -- reduces to 0!
zero' = unbox {A = Nat  Bool}
         (box {A = Nat  Nat} λ x  x) 0

Checking confluence & termination

How to prove subject reduction

The standard proof of subject reduction requires:

  • confluence of reduction
  • injectivity of Π-types

We can get injectivity of Π-types by restricting rewrite rules to new (postulated) symbols.

So all we need is a confluence checker

A dilemma of checkers

  • Most confluence checking algorithms require termination
  • Most termination checking algorithms require confluence

How to get out of this loop?

Checking confluence + termination

  • Step 1: replace full reduction relation by a deterministic rewrite strategy ⟶ₛ
  • Step 2: run termination check on , conclude termination of ⟶ₛ
  • Step 3: run confluence check on using ⟶ₛ to join critical pairs, conclude confluence of ⟶ₛ
  • Step 4: from 2 + 3, conclude termination of

Adding an equational theory

Equality is not just reduction:

  • η-equality for functions and record types
  • irrelevance and Prop

We should take these into account for rewriting!

Example

module _ (A : Set) where
  postulate
    h : (A  A)  Bool
    h-id : h  x  x)  true
    h-const :  c  h  x  c)  false
{-# REWRITE h-id    #-}
{-# REWRITE h-const #-}

Q: is this confluent?

No! true = f ⊤ (λ x → tt) = false

Conclusion

Rewrite rules are useful, but also dangerous

There are many things to take into account when checking safety of rewrite rules

We are currently working hard to add these checks to both Agda and Coq!