Verified Programming Based on Univalent Foundations in Agda - Part 2

Posted on July 6, 2020

Lists, Vectors, and Regular Expressions

Our Usual Recap and Disclaimers:

The last post delved into basic inductive types, introduced the univalent notion of equality, and proofs by case analysis. Our previous posts can be found here.

As always, our primary sources are:

I make no claim that any proof given is the best one possible regardless of your criteria of best. I’m not very good at this and don’t know Agda very well. And I definitely am not going to win style points.

We begin as usual.

{-# OPTIONS --without-K --exact-split --safe #-}

module Data where

open import Naturals

Lists

module Lists where

  data 𝕃 (A : Set ) : Set  where 
    [] : 𝕃 A
    _::_ : A  𝕃 A  𝕃 A

We have been doing heady stuff to define the logical framework we are using to reason about programs, but the objects to which we have applied this framework have so far been relatively simple: booleans, and the naturals. Lists will be our first data structure which we will seriously examine.

We now define some natural functions on lists which should be familiar to anyone who has spent a few moments doing functional programming.
  length : {A : Set }  𝕃 A  
  length [] = Z
  length (_ :: l) = S (length l)

  _++_ : {A : Set }  𝕃 A  𝕃 A  𝕃 A
  [] ++ ys = ys
  (x :: xs) ++ ys = x :: (xs ++ ys)

  infixr 20 _++_

  map : {A : Set } {B : Set ℓ'}  (A  B)
       𝕃 A  𝕃 B
  map f [] = []
  map f (x :: xs) = f x :: map f xs

The with abstraction is a convenient way to define a function where the logic is driven by an intermediary value. At its simplest we avoid calling some control flow function like if in the definition and instead get to pattern-match on the test directly. We will also see how the with abstraction will allow the Agda compiler to eliminate impossible cases for us; thus, saving us work. As a side note, I think it’s interesting the with abstraction is 21st century syntax. It was introduced in the 2004 paper The View From The Left by Conor McBride and James McKinna. The next few utilities use this mechanic.
  filter : {A : Set }  (A  𝔹)
          𝕃 A  𝕃 A
  filter p [] = []
  filter p (x :: xs) with p x
  filter p (x :: xs) | tt = x :: filter p xs
  filter p (x :: xs) | ff = filter p xs
  
  remove : {A : Set }  (A  A  𝔹)  A
          𝕃 A  𝕃 A
  remove eq a l = filter  b  ~ eq a b) l


The reader should take note that we have avoided any talk about the head or tail (car and cdr if you’re lispy) of a list. Despite being the natural accessors of lists, head and tail are not trivial because Agda is a total language. Functions cannot be partially defined, so we have to decide on what hd [] and tl [] should return. Our other option, and the one we will take, is to provide a way to signal we have reached these pathological cases.

The Maybe data type can be used to implement partial functions or to indicate a failed computation in a type-safe manner. With Maybe
  data Maybe (A : Set ) : Set  where
    Nothing : Maybe A
    Just : A  Maybe A

  tl : {A : Set }  𝕃 A  Maybe (𝕃 A)
  tl [] = Nothing
  tl (_ :: xs) = Just xs

We could have tl [] return [] but it’s not clear-cut the tail of [] should be [], and we won’t always have such a choice.
  hd : {A : Set }  𝕃 A  Maybe A
  hd [] = Nothing
  hd (x :: xs) = Just x

Unlike tl there is no canonical default option since we have no idea about a default value of A. Maybe provides a reasonable default value and a gentle exception to indicate a computation didn’t return as desired.
  nth : {A : Set }    𝕃 A  Maybe A
  nth _ [] = Nothing
  nth Z (x :: _) = Just x
  nth (S n) (_ :: xs) = nth n xs


Verifying Properties About Lists

We have enough functions on list to start proving a web of theorems.
  length-++ : {A : Set } (xs ys : 𝕃 A)
             length (xs ++ ys)  (length xs + length ys)
  length-++ [] ys = length ys ≡⟨ +-Z-l (length ys) ⁻¹ 
                           ((Z + length ys) )
  length-++ (x :: xs) ys = 
    S (length (xs ++ ys))
    ≡⟨ ap S (length-++ xs ys) 
    S (length xs + length ys)
    ≡⟨ +-S-l (length xs) (length ys) ⁻¹ 
    ((S (length xs) + length ys) ) 


  ++-assoc : {A : Set } (xs ys zs : 𝕃 A)
            ((xs ++ ys) ++ zs)  (xs ++ ys ++ zs)
  ++-assoc [] ys zs = refl _
  ++-assoc (x :: xs) ys zs =
    (x :: ((xs ++ ys) ++ zs))
    ≡⟨ ap  l  x :: l) (++-assoc xs ys zs) 
    ((x :: (xs ++ ys ++ zs)) )

  length-filter : {A : Set } (p : A  𝔹) (xs : 𝕃 A)
                 length (filter p xs)  length xs
  length-filter p [] = *
  length-filter p (x :: xs) with p x
  length-filter p (x :: xs) | tt =
    length-filter p xs
  length-filter p (x :: xs) | ff =
    ≤-tran (length (filter p xs))
           (length xs)
           (S (length xs))
           (length-filter p xs)
           (≤-S (length xs))


  filter-hd-tt : {A : Set } (p : A  𝔹) (x : A) (xs : 𝕃 A)
                (p x  tt)
                filter p (x :: xs)  (x :: filter p xs)
  filter-hd-tt p x xs q with p x
  filter-hd-tt p x xs q | tt = refl _

  filter-hd-ff : {A : Set } (p : A  𝔹) (x : A) (xs : 𝕃 A)
                (p x  ff)
                filter p (x :: xs)  (filter p xs)
  filter-hd-ff p x xs q with p x
  filter-hd-ff p x xs q | ff = refl _


We know introduce the inspect idiom. The inspect idiom is used to retain the connection between a term t and the value v that term is equal to after pattern matching, that is, we retain the knowledge t ≡ v. This is crucial for us since we aren’t using any sort of rewrite ability, so we need to the knowledge t ≡ v to implement substitutions/rewrites using transport.
  data Singleton-Type {X : Set } (x : X) : Set  where
    _with≡_ : (y : X)  x  y  Singleton-Type x

  inspect : {A : Set } (x : A)  Singleton-Type x
  inspect x = x with≡ refl x 

When doing Univalent Foundations, we sometimes are interested in a mathier formulation of singleton types derived from the elementary constructs of spartan MLTT. In that formulation, we are usually more interested in singleton-types as mathematical objects than as a programing idiom.
  singleton-type : {X : Set }  X  Set 
  singleton-type {} {X} x = Σ[ y  X ] (y  x)

With our new idiom we can now prove theorems that were previously out of reach.
  filter-idem : {A : Set } (p : A  𝔹) (xs : 𝕃 A)
               filter p (filter p xs)  filter p xs
  filter-idem p [] = refl _
  filter-idem p (x :: xs) with inspect (p x)
  filter-idem p (x :: xs) | tt with≡ q =
    filter p (filter p (x :: xs))
    ≡⟨ ap (filter p) (filter-hd-tt p x xs q) 
    filter p (x :: filter p xs)
    ≡⟨ filter-hd-tt p x (filter p xs) q 
    (x :: filter p (filter p xs))
    ≡⟨ ap  l  x :: l) (filter-idem p xs) 
    (x :: filter p xs)
    ≡⟨ filter-hd-tt p x xs q ⁻¹  
    (filter p (x :: xs)  )
  filter-idem p (x :: xs) | ff with≡ q =
    filter p (filter p (x :: xs))
    ≡⟨ ap (filter p) (filter-hd-ff p x xs q) 
    filter p (filter p xs)
    ≡⟨ filter-idem p xs 
    filter p xs
    ≡⟨ filter-hd-ff p x xs q ⁻¹ 
    (filter p (x :: xs) )

  filter-++ : {A : Set } (p : A  𝔹) (xs ys : 𝕃 A)
             filter p (xs ++ ys)  (filter p xs ++ filter p ys)
  filter-++ p [] ys = refl _
  filter-++ p (x :: xs) ys with inspect (p x)
  filter-++ p (x :: xs) ys | tt with≡ q =
    filter p (x :: (xs ++ ys))
    ≡⟨ filter-hd-tt p x (xs ++ ys) q 
    (x :: filter p (xs ++ ys))
    ≡⟨ ap  l  x :: l) (filter-++ p xs ys) 
    (x :: filter p xs) ++ filter p ys
    ≡⟨ ap  l  l ++ filter p ys) (filter-hd-tt p x xs q ⁻¹) 
    ((filter p (x :: xs) ++ filter p ys) )
  filter-++ p (x :: xs) ys | ff with≡ q =
    filter p ((x :: xs) ++ ys)
    ≡⟨ filter-hd-ff p x (xs ++ ys) q 
    filter p (xs ++ ys)
    ≡⟨ filter-++ p xs ys 
    filter p xs ++ filter p ys
    ≡⟨ ap  l  l ++ filter p ys) (filter-hd-ff p x xs q) ⁻¹ 
    ((filter p (x :: xs) ++ filter p ys) )


  map-++ : {A : Set } {B : Set ℓ'} (f : A  B) (xs ys : 𝕃 A)
          map f (xs ++ ys)  (map f xs ++ map f ys)
  map-++ f [] ys = refl _
  map-++ f (x :: xs) ys = ap  l  f x :: l) (map-++ f xs ys)

  ++-[]-r : {A : Set } (xs : 𝕃 A)
           (xs ++ [])  xs
  ++-[]-r [] = refl _
  ++-[]-r (x :: xs) = ap  l  x :: l) (++-[]-r xs)        

  map-fuse : {A : Set } {B : Set ℓ'} {C : Set ℓ''}
           (f : A  B) (g : B  C) (xs : 𝕃 A)
              map g (map f xs)  map (g  f) xs
  map-fuse f g [] = refl _
  map-fuse f g (x :: xs) =
    ap  l  g (f x) :: l) (map-fuse f g xs)            


  flatten : {A : Set }  𝕃 (𝕃 A)  𝕃 A
  flatten [] = []
  flatten (a :: as) = a ++ flatten as

  flat-map : {A : Set } {B : Set ℓ'}  (A  𝕃 B)
            𝕃 A  𝕃 B
  flat-map f [] = []
  flat-map f (x :: xs) = f x ++ flat-map f xs

  flat-map-thm : {A : Set } {B : Set ℓ'}
               (f : A  𝕃 B) (xs : 𝕃 A)
                flat-map f xs  flatten (map f xs)
  flat-map-thm f [] = refl _
  flat-map-thm f (x :: xs) =
    ap  l  f x ++ l) (flat-map-thm f xs)             



life is more verbose without rewrite
  takeWhile : {A : Set }  (A  𝔹)  𝕃 A  𝕃 A
  takeWhile p [] = []
  takeWhile p (x :: l) with p x
  takeWhile p (x :: l) | tt = x :: takeWhile p l
  takeWhile p (x :: l) | ff = []

  repeat : {A : Set }    A  𝕃 A
  repeat Z a = []
  repeat (S n) a = a :: repeat n a

  takeWhile-hd-tt : {A : Set } (p : A  𝔹)
                  (x : A) (xs : 𝕃 A)
                   p x  tt
                   takeWhile p (x :: xs)  (x :: takeWhile p xs)
  takeWhile-hd-tt p x ax q with p x
  takeWhile-hd-tt p x ax q | tt = refl _

 
  takeWhile-hd-ff : {A : Set } (p : A  𝔹) (x : A) (xs : 𝕃 A)
                   p x  ff
                   takeWhile p (x :: xs)  []
  takeWhile-hd-ff p x ax q with p x
  takeWhile-hd-ff p x ax q | ff = refl _

  takeWhile-repeat : {A : Set } (p : A  𝔹) (a : A) (n : )
                    (p a  tt )
                    takeWhile p (repeat n a)  repeat n a
  takeWhile-repeat p a Z q = refl _
  takeWhile-repeat p a (S n) q =
    takeWhile p (a :: repeat n a)
    ≡⟨ takeWhile-hd-tt p a (repeat n a) q 
    (a :: takeWhile p (repeat n a))
    ≡⟨ ap  l  a :: l) (takeWhile-repeat p a n q) 
    ((a :: repeat n a) ) 


  nth-hds : {A : Set }    𝕃 A  𝕃 A
  nth-hds _ [] = []
  nth-hds Z (_ :: _) = []
  nth-hds (S n) (x :: xs) = x :: nth-hds n xs

  nth-tls : {A : Set }    𝕃 A  𝕃 A
  nth-tls Z xs = xs
  nth-tls (S n) [] = []
  nth-tls (S n) (x :: xs) = nth-tls n xs

  ++-nth-hd-tl : {A : Set } (n : ) (xs : 𝕃 A)
                  (nth-hds n xs ++ nth-tls n xs)  xs
  ++-nth-hd-tl Z [] = refl _
  ++-nth-hd-tl Z (x :: xs) = refl _
  ++-nth-hd-tl (S n) [] = refl _
  ++-nth-hd-tl (S n) (x :: xs) =
    (x :: (nth-hds n xs ++ nth-tls n xs))
    ≡⟨ ap (x ::_) (++-nth-hd-tl n xs) 
     ((x :: xs) )

Folding

Most of the functions on lists which we have so far defined and examined can be implemented via the higher-order function fold. We define both a left and right-handed version of fold for completeness, but we will limit examination to the right-hand version.
  foldr : {A : Set } {B : Set ℓ'}  (A  B  B)  B  𝕃 A  B
  foldr _ b [] = b
  foldr f b (a :: as) = f a (foldr f b as)

  foldl : {A : Set } {B : Set ℓ'}  (B  A  B)  B  𝕃 A  B
  foldl _ b [] = b
  foldl f b (a :: as) = foldl f (f b a) as

  fold-map : {A : Set } {B : Set ℓ'}  (A  B)  𝕃 A  𝕃 B
  fold-map f  = foldr  a bs  f a :: bs) [] 

  fold-map-agrees : {A : Set } {B : Set ℓ'}
                    (f : A  B) (as : 𝕃 A)
                     fold-map f as  map f as
  fold-map-agrees f [] = refl _
  fold-map-agrees f (a :: as) =
    (f a :: fold-map f as)
    ≡⟨ ap  l  f a :: l ) (fold-map-agrees f as) 
    ((f a :: map f as) )

  fold-length : {A : Set }  𝕃 A  
  fold-length = foldr  _ n  S n) Z

  fold-length-agrees : {A : Set } (as : 𝕃 A)
                      fold-length as  length as
  fold-length-agrees [] = refl Z
  fold-length-agrees (_ :: as) =
    ap S (fold-length-agrees as)


We can the same for filter. First we define the function if. In our case, if is just a normal function and shouldn’t be considered special as an operator in any way.
  if : {A : Set }  𝔹  A  A  A
  if tt true _ = true
  if ff _ false = false

  fold-filter : {A : Set }  (A  𝔹)  𝕃 A  𝕃 A
  fold-filter f = foldr  x l  if (f x) (x :: l) l) []


  fold-filter-hd-tt : {A : Set } (f : A  𝔹) (a : A) (as : 𝕃 A)
                     f a  tt  fold-filter f (a :: as)  (a :: fold-filter f as)
  fold-filter-hd-tt f a as p with f a
  fold-filter-hd-tt f a as p | tt = refl _

  fold-filter-hd-ff : {A : Set } (f : A  𝔹) (a : A) (as : 𝕃 A)
                     f a  ff  fold-filter f (a :: as)  fold-filter f as
  fold-filter-hd-ff f a as p with f a
  fold-filter-hd-ff f a as p | ff = refl _                    

  fold-filter-agrees : {A : Set } (f : A  𝔹) (as : 𝕃 A)
                      fold-filter f as  filter f as
  fold-filter-agrees f [] = refl []
  fold-filter-agrees f (a :: as) with inspect (f a)
  fold-filter-agrees f (a :: as) | tt with≡ p =
    fold-filter f (a :: as)
    ≡⟨ fold-filter-hd-tt f a as p 
    (a :: fold-filter f as)
    ≡⟨ ap (a ::_) (fold-filter-agrees f as) 
    (a :: filter f as)
    ≡⟨ filter-hd-tt f a as p ⁻¹ 
    (filter f (a :: as) )
  fold-filter-agrees f (a :: as) | ff with≡ p =
    fold-filter f (a :: as)
    ≡⟨ fold-filter-hd-ff f a as p 
    fold-filter f as
    ≡⟨ fold-filter-agrees f as 
    filter f as
    ≡⟨ filter-hd-ff f a as p ⁻¹ 
    (filter f (a :: as) )

A common technique in verified programming is to define two versions of a function: one that is conceptually simple whose structure easily lends itself to proofs, and another which is more efficient but maybe harder to reason about. If we can show our two definitions coincide then we can transport any property about the first function to a property about the second. Let’s define two versions of a function that reverses a list.
  slow-reverse : {A : Set }  𝕃 A  𝕃 A
  slow-reverse [] = []
  slow-reverse (x :: xs) = slow-reverse xs ++ (x :: [])

  reverse-helper : {A : Set }  𝕃 A  𝕃 A  𝕃 A
  reverse-helper xs [] = xs
  reverse-helper xs (y :: ys) = reverse-helper (y :: xs) ys

  reverse : {A : Set }  𝕃 A  𝕃 A
  reverse xs = reverse-helper [] xs

  reverse-lemma : {A : Set } (xs ys : 𝕃 A)
                 reverse-helper xs ys  (reverse-helper [] ys ++ xs)
  reverse-lemma xs [] = refl _
  reverse-lemma xs (y :: ys) =
    reverse-helper (y :: xs) ys
    ≡⟨ reverse-lemma (y :: xs) ys 
    reverse-helper [] ys ++ (y :: xs)
    ≡⟨ ++-assoc (reverse-helper [] ys) (y :: []) xs ⁻¹ 
    (reverse-helper [] ys ++ (y :: [])) ++ xs
    ≡⟨ ap (_++ xs) (reverse-lemma (y :: []) ys ⁻¹) 
    ((reverse-helper (y :: []) ys ++ xs) )                

  revs-agree : {A : Set } (xs : 𝕃 A)
              reverse xs  slow-reverse xs
  revs-agree [] = refl []
  revs-agree (x :: xs) =
    reverse-helper (x :: []) xs
    ≡⟨ reverse-lemma (x :: []) xs 
    reverse-helper [] xs ++ (x :: [])
    ≡⟨ ap (_++ (x :: [])) (revs-agree xs) 
    ((slow-reverse xs ++ (x :: [])) )


We then prove properties about reverse by transporting results about slow-reverse.
  map-srev : {A : Set } {B : Set ℓ'} (f : A  B) (xs : 𝕃 A)
            map f (slow-reverse xs)  slow-reverse (map f xs)
  map-srev f [] = refl _
  map-srev f (x :: xs) =
    map f (slow-reverse xs ++ (x :: []))
    ≡⟨ map-++ f (slow-reverse xs) (x :: []) 
    map f (slow-reverse xs) ++ (f x :: [])
    ≡⟨ ap  l  l ++ (f x :: [])) (map-srev f xs) 
    ((slow-reverse (map f xs) ++ (f x :: [])) )

  map-rev : {A : Set } {B : Set ℓ'} (f : A  B) (xs : 𝕃 A)
            map f (reverse xs)  reverse (map f xs)
  map-rev f xs = map f (reverse xs)
                 ≡⟨ ap (map f) (revs-agree xs) 
                 map f (slow-reverse xs)
                 ≡⟨ map-srev f xs 
                 slow-reverse (map f xs)
                 ≡⟨ revs-agree (map f xs) ⁻¹ 
                 (reverse (map f xs) )

  length-srev : {A : Set } (xs : 𝕃 A)
               length (slow-reverse xs)  length xs
  length-srev [] = refl _
  length-srev (x :: xs) =
    length (slow-reverse xs ++ (x :: []))
    ≡⟨ length-++ (slow-reverse xs) (x :: []) 
    S (length (slow-reverse xs))
    ≡⟨ ap S (length-srev xs) 
    (S (length xs) )

  length-rev : {A : Set } (xs : 𝕃 A)
              length  (reverse xs)  length xs 
  length-rev xs = length (reverse xs)
                  ≡⟨ ap length (revs-agree xs) 
                  length-srev xs             

  srev-++ : {A : Set } (xs ys : 𝕃 A)
           slow-reverse (xs ++  ys)  (slow-reverse ys ++ slow-reverse xs) 
  srev-++ [] ys = ++-[]-r (slow-reverse ys) ⁻¹
  srev-++ (x :: xs) ys =
    slow-reverse (xs ++ ys) ++ (x :: [])
    ≡⟨ ap  l  l ++ (x :: [])) (srev-++ xs ys) 
    ((slow-reverse ys ++ slow-reverse xs)) ++ (x :: [])
    ≡⟨ ++-assoc (slow-reverse ys) (slow-reverse xs) (x :: []) 
    ((slow-reverse ys ++ slow-reverse xs ++ (x :: [])) )

  rev-++ : {A : Set } (xs ys : 𝕃 A)
          reverse (xs ++ ys)  (reverse ys ++ reverse xs)
  rev-++ xs ys  =
    reverse (xs ++ ys)
    ≡⟨ revs-agree (xs ++ ys) 
    slow-reverse (xs ++ ys)
    ≡⟨ srev-++ xs ys 
    slow-reverse ys ++ slow-reverse xs
    ≡⟨ κ₁  κ₂ 
    ((reverse ys ++ reverse xs) )
      where
        κ₁ =  ap (slow-reverse ys ++_) (revs-agree xs ⁻¹)
        κ₂ =  ap (_++ reverse xs) (revs-agree ys ⁻¹)


Dependent Data Types

We have the full power of a dependent type system, and we should use it. Vectors are lists which carry additional information about its length.
module Vectors where

  data 𝕍 (A : Set ) :   Set  where
    [] : 𝕍 A Z
    _::_ : {n : }  A  𝕍 A n  𝕍 A (S n)

With this definition, certain functions become trivial. For example we don’t even need to inspect a vector to determine its length.
  length : {A : Set } {n : }  𝕍 A n  
  length {_} {_} {n} _ = n

We do have to approach definitions more carefully.
  _++_ : {A : Set } {m n : }  𝕍 A m  𝕍 A n  𝕍 A (m + n)
  [] ++ ys = transport (𝕍 _) (+-Z-l _ ⁻¹) ys
  (x :: xs) ++ ys =
    transport (𝕍 _) (+-S-l _ _ ⁻¹) (x :: (xs ++ ys))

We could have defined _++_ like this:
  _++'_ : {A : Set } {m n : }  𝕍 A m  𝕍 A n  𝕍 A (n + m)
  [] ++' ys = ys
  (x :: xs) ++' ys = x :: (xs ++' ys)

Notice the inversion and the resulting vector from append has length n + m and not ‘m + n’. We defined induction for addition on the right argument and for append on the left argument. Now you and I know ‘m + n’ and ’n + m` are equal, and have proved it, but they are not definitionally or trivially equal which causes the Agda compiler to complain. Now we could have gone back and defined addition as inductive on the left argument and then we could avoid the swap when defining append. And that actually would have been a good idea but here are possible counter-arguments.

  1. It is a fair amount of work to rewrite and those would not be billable hours.
  2. I’ve been defining addition via induction on the right argument for too many years to change my ways (Enderton got to me first).
  3. It’s a problem we would eventually come to at some point. Append is more naturally defined on the left, while there doesn’t seem to be any reason to prefer a side when defining addition. Of course there are a lot of functions in the wild and it would be impossible to not eventually come across this stumbling block.
  4. Nothing matters. Arity is lie, there is only currying, mixfix is a lie, there is only prefix. Syntactic sugar is a sin and our problems are its wages.
We can define map the same way as before and some previous theorems fall out.
  map : {A : Set } {B : Set ℓ'} {n : }
       (A  B)  𝕍 A n  𝕍 B n
  map f [] = []
  map f (x :: xs) = f x :: map f xs

  length-map : {A : Set } {B : Set ℓ'}
             {n : } (f : A  B) (xs : 𝕍 A (S n)) 
              length (map f xs)  (length xs)
  length-map _ _ = refl _

We can use the index vectors carry to write hd and tl in a total and type-safe manner without having to use the Maybe data type.
  hd : {A : Set } {n : }  𝕍 A (S n)  A
  hd (x :: _) = x

  tl : {A : Set } {n : }  𝕍 A (S n)  𝕍 A n
  tl (_ :: xs) = xs

  hd-tl-verify : {A : Set } {n : } (xs : 𝕍 A (S n))
                (hd xs :: tl xs)  xs
  hd-tl-verify (x :: xs) = refl _

We continue porting out list functions to our vector type.
  concat : {A : Set } {m n : }  𝕍 (𝕍 A n) m  𝕍 A (n × m)
  concat [] = []
  concat (xs :: ys) = xs ++ concat ys

  nth : {A : Set } {m : } (n : )  (p : n < m)  𝕍 A m  A
  nth Z _ (x :: _) = x
  nth (S n) p (x :: xs) = nth n p xs


  repeat : {A : Set } (n : )  A  𝕍 A n
  repeat Z _ = []
  repeat (S n) a = a :: repeat n a

  zip-with : {A : Set } {B : Set ℓ'} {C : Set ℓ''} {m : }
            (A  B  C)
            𝕍 A m  𝕍 B m  𝕍 C m
  zip-with _ [] [] = []
  zip-with f (x :: xs) (y :: ys) = f x y :: zip-with f xs ys

But what about a function like filter? Filter neither preserves function length and the length of the output can depend on information only known at runtime. How do we write a type-safe version on vectors when we don’t and can’t know the elements of our list, nor our filter f, at compile time? We have filter not just return a list but a more complicated data structure, which can also carry some internal verification.

Syntax note: we can nest with abstractions.
  filter : {A : Set } {n : } (f : A  𝔹)  𝕍 A n
          Σ[ k   ] ((k  n)  𝕍 A k )
  filter f [] = Z , * , []
  filter f (x :: l) with (f x) | (filter f l)
  filter f (x :: l) | tt | k , p , res =
    S k , p , (x :: res)
  filter f l@(x :: l') | ff | k , p , res =
    k , ≤-tran k _ _ p (≤-S (length l)) , res

Regular Expressions

We could continue and examine matrices or trees, but I honestly had more fun with regular expressions.
module RegularExpressions where
  open Lists public

We define regular expressions over an arbitrary type A.
  data RegExp (A : Set ) : Set  where
     EmptySet : RegExp A
     EmptyStr : RegExp A
     Char : A  RegExp A
     App : RegExp A  RegExp A  RegExp A
     Union : RegExp A  RegExp A  RegExp A
     Star : RegExp A  RegExp A

And we define a type which represents proofs that a string (in the general sense of a finite sequence of objects) matches a certain regular expression.
  data ExpMatch {A : Set } : 𝕃 A  RegExp A  Set  where
    MEmpty : ExpMatch [] EmptyStr
    MChar : (x : A)  ExpMatch (x :: []) (Char x)
    MApp : (l₁ : 𝕃 A) (re₁ : RegExp A)
         (l₂ : 𝕃 A) (re₂ : RegExp A)
         (M₁ : ExpMatch l₁ re₁) (M₂ : ExpMatch l₂ re₂)
           ExpMatch (l₁ ++ l₂) (App re₁ re₂)
    MUnionL :  (l₁ : 𝕃 A) (re₁ : RegExp A) (re₂ : RegExp A)
         (M₁ : ExpMatch l₁ re₁) 
           ExpMatch l₁ (Union re₁ re₂)
    MUnionR : (re₁ : RegExp A) (l₂ : 𝕃 A) (re₂ : RegExp A)
         (M₂ : ExpMatch l₂ re₂)
           ExpMatch l₂ (Union re₁ re₂)
    MStarZ : (re : RegExp A)  ExpMatch [] (Star re)
    MStarS : (l₁ l₂ : 𝕃 A) (re : RegExp A)
           (M₁ : ExpMatch l₁ re) (M₂ : ExpMatch l₂ (Star re))
            ExpMatch (l₁ ++ l₂) (Star re)

For notational convenience we define

  _=~_ : {A : Set } (l : 𝕃 A) (re : RegExp A)  Set 
  s =~ re = ExpMatch s re

We can define a utility to create a regular expression from a list that matches that list.
  listToRegExp : {A : Set }  (𝕃 A)  RegExp A
  listToRegExp [] = EmptyStr
  listToRegExp (x :: l) = App (Char x) (listToRegExp l)

  listToRegExpMatch : {A : Set } (l : 𝕃 A)
                     l =~ listToRegExp l
  listToRegExpMatch [] = MEmpty
  listToRegExpMatch (x :: l) =
    MApp (x :: []) (Char x) l (listToRegExp l)
         (MChar x) (listToRegExpMatch l)

We also prove some miscellaneous results.
  emptyMatchesStar : {A : Set } (re : RegExp A)
     [] =~ Star re
  emptyMatchesStar = MStarZ 

  matchThenMatchStar : {A : Set } (s : 𝕃 A) (re : RegExp A)
                      s =~ re  s =~ Star re
  matchThenMatchStar s re p =
    transport  -  ExpMatch - (Star re) ) (++-[]-r s) res
    where
      res : ExpMatch (s ++ []) (Star re)
      res = MStarS s [] re p (emptyMatchesStar re) 

We have to take some care to avoid the absurd () pattern and instead rely on univalent reasoning.
  notEmptySet : {A : Set }  RegExp A  𝔹
  notEmptySet EmptySet = ff
  notEmptySet EmptyStr = tt
  notEmptySet (Char _) = tt
  notEmptySet (App _ _) = tt
  notEmptySet (Union _ _) = tt
  notEmptySet (Star _) = tt

  ifMatchThenNotEmptySet : {A : Set } (s : 𝕃 A)
                         (re : RegExp A)  (s =~ re)
                          re  EmptySet
  ifMatchThenNotEmptySet _ _ MEmpty p =
    tt-≢-ff (ap notEmptySet p)
  ifMatchThenNotEmptySet _ _ (MChar _) p =
    tt-≢-ff (ap notEmptySet p)
  ifMatchThenNotEmptySet _ _ (MApp _ _ _ _ _ _) p =
    tt-≢-ff (ap notEmptySet p)
  ifMatchThenNotEmptySet _ _ (MUnionL _ _ _ _) p =
    tt-≢-ff (ap notEmptySet p)
  ifMatchThenNotEmptySet _ _ (MUnionR _ _ _ _) p =
    tt-≢-ff (ap notEmptySet p)
  ifMatchThenNotEmptySet _ _ (MStarZ _) p =
    tt-≢-ff (ap notEmptySet p)
  ifMatchThenNotEmptySet _ _ (MStarS _ _ _ _ _) p =
    tt-≢-ff (ap notEmptySet p)
  
  nothingMatchesEmptySet : {A : Set } (s : 𝕃 A)
                          ¬ (s =~ EmptySet)
  nothingMatchesEmptySet s p = u (refl EmptySet)
    where
      u : EmptySet  EmptySet 
      u = ifMatchThenNotEmptySet s EmptySet p

  matchOneMatchUnion : {A : Set } (s : 𝕃 A)
                     (re₁ re₂ : RegExp A)
                      (s =~ re₁)  (s =~ re₂)
                      s =~ Union re₁ re₂
  matchOneMatchUnion s re₁ re₂ (inl p) = MUnionL s re₁ re₂ p
  matchOneMatchUnion s re₁ re₂ (inr q) = MUnionR re₁ s re₂ q

In carries proof that an element is indeed present in a list. It’s a useful type when reasoning about lists in general and regular expressions in particular.

  data In {A : Set } : A  𝕃 A  Set  where
    Here : {x : A} {l : 𝕃 A}  In x (x :: l)
    There : {x y : A} {l : 𝕃 A}  In x l  In x (y :: l)

  holdsAllholdsTail : {A : Set } (B : A  Set ℓ')
                    {x : A} {l : 𝕃 A}
                    ((y : A)  In y (x :: l)  B y)
                    (y : A)  In y l  B y
  holdsAllholdsTail B f y p = f y (There p)
    

  eachMatchThenMatchStar : {A : Set } (ss : 𝕃 (𝕃 A))
                         (re : RegExp A)
                          ((s : 𝕃 A)  In s ss  s =~ re)
                          flatten ss =~ Star re
  eachMatchThenMatchStar [] re f = emptyMatchesStar re
  eachMatchThenMatchStar (s :: ss) re f =
    MStarS s
           (flatten ss)
           re
           (f s Here)
           (eachMatchThenMatchStar ss re g)
      where
        g = holdsAllholdsTail (_=~ re) f

There are partial inverse results which tell us the necessary form a string must have in order to match a regular expression.

What I find interesting is how little we need. We don’t need to require the underlying set A to be finite, or even to have decidable equality in order to reason about regular expressions and strings over A.
  emptyStrSplit : {A : Set } (s : 𝕃 A)  s =~ EmptyStr
                 s  []
  emptyStrSplit .[] MEmpty = refl _              

  charSplit : {A : Set } (x : A) (s : 𝕃 A)  s =~ Char x
             s  (x :: [])
  charSplit x .(x :: []) (MChar .x) = refl _

  onlyArgMatchesListToExp : {A : Set } (s₁ s₂ : 𝕃 A)
                         s₁ =~ listToRegExp s₂  s₁  s₂
  onlyArgMatchesListToExp s₁ s₂ p
    with inspect (listToRegExp s₂)
  onlyArgMatchesListToExp .[] [] MEmpty |
    EmptyStr with≡ refl .EmptyStr = refl []
  onlyArgMatchesListToExp .(l₁ ++ l₂) (x :: s₂)
                          (MApp l₁ .(Char x) l₂
                          .(listToRegExp s₂) p₁ p₂) |
                          App (Char _) _ with≡ _ =
    l₁ ++ l₂
    ≡⟨ ap (_++ l₂) (charSplit x l₁ p₁) 
    (x :: []) ++ l₂
    ≡⟨ ap ((x :: []) ++_) (onlyArgMatchesListToExp l₂ s₂ p₂) 
    ((x :: s₂) )

We can also deduce information about the regular expression re from a string s that matches with it.
  regExpChars : {A : Set }  RegExp A  𝕃 A
  regExpChars EmptySet = []
  regExpChars EmptyStr = []
  regExpChars (Char x) = x :: []
  regExpChars (App re₁ re₂) =
    regExpChars re₁ ++ regExpChars re₂
  regExpChars (Union re₁ re₂) =
    regExpChars re₁ ++ regExpChars re₂
  regExpChars (Star re) = regExpChars re

  inSplit : {A : Set } (x : A) (l₁ l₂ : 𝕃 A)
           In x (l₁ ++ l₂)
           In x l₁  In x l₂
  inSplit x [] l₂ p = inr p
  inSplit x (.x :: l₁) l₂ Here = inl Here
  inSplit x (y :: l₁) l₂ (There p) with (inSplit x l₁ l₂ p)
  inSplit x (y :: l₁) l₂ (There p) | inl q = inl (There q)
  inSplit x (y :: l₁) l₂ (There p) | inr q = inr q

  inAppL : {A : Set } (x : A) (l₁ l₂ : 𝕃 A)  In x l₁
          In x (l₁ ++ l₂)
  inAppL x .(x :: _) l₂ Here = Here
  inAppL x .(_ :: _) l₂ (There p) = There (inAppL x _ l₂ p)

  inAppR : {A : Set } (x : A) (l₁ l₂ : 𝕃 A)  In x l₂
          In x (l₁ ++ l₂)
  inAppR _ [] _ p = p
  inAppR x (_ :: l₁) l₂ p = There (inAppR x l₁ l₂ p)
  

  inRegMatch : {A : Set } (x : A) (s : 𝕃 A) (re : RegExp A)
              s =~ re  In x s  In x (regExpChars re)
  inRegMatch x .[] EmptyStr MEmpty q = q
  inRegMatch .x₁ .(x₁ :: []) (Char x₁) (MChar .x₁) Here = Here
  inRegMatch x .(l₁ ++ l₂) (App re₁ re₂)
             (MApp l₁ .re₁ l₂ .re₂ p₁ p₂) q
             with (inSplit x l₁ l₂ q)
  inRegMatch x .(l₁ ++ l₂) (App re₁ re₂)
             (MApp l₁ .re₁ l₂ .re₂ p₁ p₂) q | inl q' =
    inAppL x (regExpChars re₁) (regExpChars re₂)
           (inRegMatch x l₁ re₁ p₁ q')
  inRegMatch x .(l₁ ++ l₂) (App re₁ re₂)
             (MApp l₁ .re₁ l₂ .re₂ p₁ p₂) q | inr q' =
    inAppR x (regExpChars re₁) (regExpChars re₂)
           (inRegMatch x l₂ re₂ p₂ q')
  inRegMatch x s (Union re₁ re₂) (MUnionL .s .re₁ .re₂ p) q =
    inAppL x (regExpChars re₁) (regExpChars re₂)
           (inRegMatch x s re₁ p q)
  inRegMatch x s (Union re₁ re₂) (MUnionR .re₁ .s .re₂ p) q =
    inAppR x (regExpChars re₁) (regExpChars re₂)
           (inRegMatch x s re₂ p q)
  inRegMatch x .(l₁ ++ l₂) (Star re)
             (MStarS l₁ l₂ .re p₁ p₂) q
             with (inSplit x l₁ l₂ q)
  inRegMatch x .(l₁ ++ l₂) (Star re)
             (MStarS l₁ l₂ .re p₁ p₂) q | inl q' =
    inRegMatch x l₁ re p₁ q'
  inRegMatch x .(l₁ ++ l₂) (Star re)
             (MStarS l₁ l₂ .re p₁ p₂) q | inr q' =
    inRegMatch x l₂ (Star re) p₂ q'

We can also tell if from the structure of a regular expression re wether there are any strings which match with it.
  regExpNotEmpty : {A : Set }  RegExp A  𝔹
  regExpNotEmpty EmptySet = ff
  regExpNotEmpty EmptyStr = tt
  regExpNotEmpty (Char _) = tt
  regExpNotEmpty (App re₁ re₂) =
    regExpNotEmpty re₁ && regExpNotEmpty re₂
  regExpNotEmpty (Union re₁ re₂) =
    regExpNotEmpty re₁ || regExpNotEmpty re₂
  regExpNotEmpty (Star re) = tt

  &&-to-∧ : (b c : 𝔹)  (b && c)  tt
           (b  tt)  (c  tt)
  &&-to-∧ tt tt (refl _) = refl _ , refl _

  ||-to-∨ : (b c : 𝔹)  (b || c)  tt
           (b  tt)  (c  tt)
  ||-to-∨ tt _ _ = inl (refl _)
  ||-to-∨ ff tt _ = inr (refl _)


And now to verify regExpNotEmpty is correct. This one is a bit of a beast, but that is undoubtedly due to my own lack of ability.
  regExpNotEmptyCorrect : {A : Set } (re : RegExp A)
                         (Σ[ s  𝕃 A ] (s =~ re))
                               (regExpNotEmpty re  tt)
  regExpNotEmptyCorrect {} {A} re = f re , g re
    where
      f : (re : RegExp A)  (Σ[ s  𝕃 A ] (s =~ re))
                           (regExpNotEmpty re  tt)
      f .EmptyStr (.[] , MEmpty) = refl _
      f .(Char x) (.(x :: []) , MChar x) = refl _
      f .(App re₁ re₂)
        (.(l₁ ++ l₂) , MApp l₁ re₁ l₂ re₂ p₁ p₂) =
          regExpNotEmpty re₁ && regExpNotEmpty re₂
          ≡⟨ ap (_&& regExpNotEmpty re₂) (f re₁ (l₁ , p₁)) 
          tt && regExpNotEmpty re₂ ≡⟨ f re₂ (l₂ , p₂) 
          (tt )       
      f .(Union re₁ re₂) (s , MUnionL .s re₁ re₂ p) =
                regExpNotEmpty re₁ || regExpNotEmpty re₂
                ≡⟨ ap (_|| regExpNotEmpty re₂) (f re₁ (s , p)) 
                tt || regExpNotEmpty re₂ ≡⟨ refl _ 
                (tt )
      f .(Union re₁ re₂) (s , MUnionR re₁ .s re₂ p) =
                regExpNotEmpty re₁ || regExpNotEmpty re₂
                ≡⟨ ||-comm (regExpNotEmpty re₂) (regExpNotEmpty re₁) 
                regExpNotEmpty re₂ || regExpNotEmpty re₁
                ≡⟨ ap (_|| regExpNotEmpty re₁) (f re₂ (s , p)) 
                tt || regExpNotEmpty re₁
                ≡⟨ refl _ 
                (tt )
      f .(Star re) (.[] , MStarZ re) = refl _
      f .(Star re) (.(l₁ ++ l₂) , MStarS l₁ l₂ re p p₁) = refl _
      
      g : (re : RegExp A)  regExpNotEmpty re  tt
                           Σ[ s  𝕃 A ] (s =~ re)
      g EmptyStr p = [] , MEmpty
      g (Char x) p = (x :: []) , MChar x
      g (App re₁ re₂) p
        with &&-to-∧ (regExpNotEmpty re₁) (regExpNotEmpty re₂) p
      g (App re₁ re₂) p | q₁ , q₂ =
        fst t₁ ++ fst t₂
        , MApp (fst t₁) re₁ (fst t₂) re₂ (snd t₁) (snd t₂)
          where
            t₁ : Σ[ s  𝕃 A ] (s =~ re₁)
            t₁ = g re₁ q₁

            t₂ : Σ[ s  𝕃 A ] (s =~ re₂)
            t₂ = g re₂ q₂
      g (Union re₁ re₂) p
        with ||-to-∨ (regExpNotEmpty re₁) (regExpNotEmpty re₂) p
      g (Union re₁ re₂) p | inl q =
        fst t , MUnionL (fst t) re₁ re₂ (snd t)
          where
            t : Σ[ s  𝕃 A ] (s =~ re₁)
            t = g re₁ q
      g (Union re₁ re₂) p | inr q =
        fst t , MUnionR re₁ (fst t) re₂ (snd t)
          where
            t : Σ[ s  𝕃 A ] (s =~ re₂)
            t = g re₂ q
      g (Star re) p = [] , MStarZ re


Here are some more results and lemmas. A lot of the code is here because it was fun to write. Not everything is useful, but there are more important things than utility.
  starApp : {A : Set } (s₁ s₂ : 𝕃 A) (re : RegExp A)
           s₁ =~ Star re  s₂ =~ Star re
           (s₁ ++ s₂) =~ Star re
  starApp .[] s₂ re (MStarZ .re) p₂ = p₂
  starApp .(l₁ ++ l₂) s₂ re (MStarS l₁ l₂ .re p₁ p₂) p₃ =
    transport  -  - =~ Star re) (++-assoc l₁ l₂ s₂ ⁻¹) ih
    where
      ih : (l₁ ++ l₂ ++ s₂) =~ Star re
      ih = MStarS l₁ (l₂ ++ s₂) re p₁ (starApp l₂ s₂ re p₂ p₃) 

  inContra : {A : Set } (x : A) (s : 𝕃 A)  In x s  s  []
  inContra {} {A} x .(x :: _) Here u = 𝟙-≢-𝟘 (ap f u)
    where
      f : 𝕃 A  Set
      f [] = 𝟘
      f (_ :: _) = 𝟙
  inContra {} {A} x .(_ :: _) (There p) u = 𝟙-≢-𝟘 (ap f u)
    where
      f : 𝕃 A  Set
      f [] = 𝟘
      f (_ :: _) = 𝟙

  starConverse : {A : Set } (s : 𝕃 A) (re : RegExp A)
                (s =~ Star re)
                Σ[ ss  𝕃 (𝕃 A) ]
                    ((s  flatten ss)
                      ((s' : 𝕃 A)  In s' ss  s' =~ re))
  starConverse .[] re (MStarZ .re) = [] , refl _ , f
    where
      f : (s' : 𝕃 _)  In s' []  s' =~ re
      f s' p = !𝟘 (inContra s' [] p (refl []))
  starConverse .(l₁ ++ l₂) re (MStarS l₁ l₂ .re p₁ p₂) =
    (l₁ :: ss') , ap (l₁ ++_) p' , g               
      where
        ih : Σ[ ss  𝕃 (𝕃 _) ]
                ((l₂  flatten ss)
                 ((s' : 𝕃 _)  In s' ss  s' =~ re))
        ih = starConverse l₂ re p₂

        ss' : 𝕃 (𝕃 _)
        ss' = fst ih

        p' : l₂  flatten ss'
        p' = fst (snd ih)

        f : (s' : 𝕃 _)  In s' ss'  s' =~ re
        f = snd (snd ih)

        g : (s' : 𝕃 _)  In s' (l₁ :: ss')  s' =~ re
        g s' Here = p₁
        g s' (There p) = f s' p

The Pumping Lemma is one of the first big results one learns about regular languages, and is incredibly useful for proving a language is not regular.

The idea behind the pumping lemma is that if we have a large enough string \(s\) which matches our regular expression , then we can partition \(s\) into three parts (\(s ≡ s₁s₂s₃\)) such that \(s₂\) isn’t empty and \(s₁s₂ⁿs₃\) for all \(n\) greater than some pumping constant. Furthermore, this pumping constant is depends on the regular expression and not \(s\), and this pumping constant bounds the length of \(s₁s₂\).

First, some results on we will use in our proof of the pumping lemma.
  max :     
  max Z Z = Z
  max Z n@(S n') = n
  max m@(S m') Z = m
  max (S m) (S n) = S (max m n)

  ≤-max-l : (m n : )  m  max m n
  ≤-max-l Z _ = *
  ≤-max-l m@(S m') Z = ≤-refl m
  ≤-max-l (S m) (S n) = ≤-max-l m n

  ≤-max-r : (m n : )  n  max m n
  ≤-max-r _ Z = *
  ≤-max-r Z n@(S n') = ≤-refl n
  ≤-max-r (S m) (S n) = ≤-max-r m n

  +-≤-l : (m n : )  m  (m + n)
  +-≤-l m Z = ≤-refl m
  +-≤-l m (S n) = ≤-tran m (m + n) (m + S n)
                         (+-≤-l m n) (≤-S (m + n))

  <-implies-≤ : (m n : )  m < n  m  n
  <-implies-≤ Z (S _) _ = *
  <-implies-≤ (S m) (S n) p = <-implies-≤ m n p

  ℕ-split : (m n : )  (m  n)  (n < m)
  ℕ-split Z n = inl *
  ℕ-split (S m) Z = inr *
  ℕ-split (S m) (S n) with ℕ-split m n
  ℕ-split (S m) (S n) | inl q = inl q
  ℕ-split (S m) (S n) | inr q = inr q

The pumping constant is how we rule out cases that otherwise contradict the pumping lemma. For instance, [x] =~ Char x, but of course [x x x x x x x] does not match against Char x.

We’re aiming to prove as strong of a pumping lemma as possible and to choose the tightest pumping constants we can.
  pumpingConstant : {A : Set }  RegExp A  
  pumpingConstant EmptySet = 0
  pumpingConstant EmptyStr = 1
  pumpingConstant (Char _) = 2
  pumpingConstant (App re₁ re₂) =
    pumpingConstant re₁ + pumpingConstant re₂
  pumpingConstant (Union re₁ re₂) =
    max (pumpingConstant re₁) (pumpingConstant re₂)
  pumpingConstant (Star re) =
    max 1 (pumpingConstant re)

  list-repeat : {A : Set }    𝕃 A  𝕃 A
  list-repeat Z _ = []
  list-repeat (S n) s = s ++ list-repeat n s

  pumpingLemma : {A : Set } (re : RegExp A) (s : 𝕃 A)
                s =~ re
                pumpingConstant re  length s
                Σ[ s₁  𝕃 A ] Σ[ s₂  𝕃 A ] Σ[ s₃  𝕃 A ]
                    ((s  (s₁ ++ s₂ ++ s₃))
                     (s₂  [])
                     (length (s₁ ++ s₂)  pumpingConstant re)
                     ((m : )
                           (s₁ ++ list-repeat m s₂ ++ s₃)
                                =~ re))
We perform induction on s =~ re. The case of union is pretty straight-forward.
  pumpingLemma .(Union re₁ re₂) s (MUnionL .s re₁ re₂ ma) p
    with pumpingLemma re₁ s ma p'
      where
        m₁ m₂ : 
        m₁ = pumpingConstant re₁
        m₂ = pumpingConstant re₂
        p' : m₁  length s
        p' = ≤-tran m₁ (max m₁ m₂) (length s)
                    (≤-max-l m₁ m₂) p 
  pumpingLemma .(Union re₁ re₂) s (MUnionL .s re₁ re₂ ma) p |
    s₁ , s₂ , s₃ , η , η' , η'' , f =
      s₁ , s₂ , s₃ , η , η' , κ , g
        where
          m₁ m₂ : 
          m₁ = pumpingConstant re₁
          m₂ = pumpingConstant re₂
          κ : length (s₁ ++ s₂)  max m₁ m₂
          κ = ≤-tran (length (s₁ ++ s₂)) m₁ (max m₁ m₂)
                     η'' (≤-max-l m₁ m₂)
          g : (k : )  (s₁ ++ list-repeat k s₂ ++ s₃)
                        =~ Union re₁ re₂
          g k = MUnionL (s₁ ++ list-repeat k s₂ ++ s₃)
                re₁ re₂ (f k)
  pumpingLemma .(Union re₁ re₂) s (MUnionR re₁ .s re₂ ma) p
    with pumpingLemma re₂ s ma p'
      where
        m₁ m₂ : 
        m₁ = pumpingConstant re₁
        m₂ = pumpingConstant re₂
        p' : m₂  length s
        p' = ≤-tran m₂ (max m₁ m₂) (length s)
                    (≤-max-r m₁ m₂) p 
  pumpingLemma .(Union re₁ re₂) s (MUnionR re₁ .s re₂ ma) p |
    s₁ , s₂ , s₃ , η , η' , η'' , f =
      s₁ , s₂ , s₃ , η , η' , κ , g
        where
          m₁ m₂ : 
          m₁ = pumpingConstant re₁
          m₂ = pumpingConstant re₂
          κ : length (s₁ ++ s₂)  max m₁ m₂
          κ = ≤-tran (length (s₁ ++ s₂)) m₂ (max m₁ m₂)
              η'' (≤-max-r m₁ m₂)
          g : (k : )  (s₁ ++ list-repeat k s₂ ++ s₃)
                        =~ Union re₁ re₂
          g k = MUnionR re₁ (s₁ ++ list-repeat k s₂ ++ s₃)
                re₂ (f k)
The case of App is a bit more involved.
  pumpingLemma .(App re₁ re₂) .(l₁ ++ l₂)
               (MApp l₁ re₁ l₂ re₂ ma₁ ma₂) p
               with (ℕ-split m₁ n₁) 
    where
      n₁ m₁ : 
      n₁ = length l₁
      m₁ = pumpingConstant re₁
We first consider the case where length l₁ is greater than or equal to the pumping constant of re₁.
  pumpingLemma .(App re₁ re₂) .(l₁ ++ l₂)
               (MApp l₁ re₁ l₂ re₂ ma₁ ma₂) p | inl p'
               with pumpingLemma re₁ l₁ ma₁ p'
  pumpingLemma .(App re₁ re₂) .(l₁ ++ l₂)
               (MApp l₁ re₁ l₂ re₂ ma₁ ma₂) p | inl p' |
               s₁ , s₂ , s₃ , η , η' , η'' , f =
    s₁ , s₂ , s₃ ++ l₂ , τ , η' , κ , g
      where
        m₁ m₂ : 
        m₁ = pumpingConstant re₁
        m₂ = pumpingConstant re₂
        τ : (l₁ ++ l₂)  (s₁ ++ s₂ ++ s₃ ++ l₂)
        τ = l₁ ++ l₂
            ≡⟨ ap (_++ l₂) η 
            (s₁ ++ s₂ ++ s₃) ++ l₂
            ≡⟨ ap (_++ l₂) (++-assoc s₁ s₂ s₃ ⁻¹) 
            ((s₁ ++ s₂) ++ s₃) ++ l₂
            ≡⟨ ++-assoc (s₁ ++ s₂) s₃ l₂ 
            (s₁ ++ s₂) ++ s₃ ++ l₂
            ≡⟨ ++-assoc s₁ s₂ (s₃ ++ l₂) 
            ((s₁ ++ s₂ ++ s₃ ++ l₂) )
        κ : length (s₁ ++ s₂)  (m₁ + m₂)
        κ = ≤-tran (length (s₁ ++ s₂)) m₁ (m₁ + m₂)
                   η'' (+-≤-l m₁ m₂)
        g : (k : )  (s₁ ++ list-repeat k s₂ ++ s₃ ++ l₂)
                      =~ App re₁ re₂
        g k = transport (_=~ App re₁ re₂) ι
                        (MApp (s₁ ++ list-repeat k s₂ ++ s₃)
                              re₁ l₂ re₂ (f k) ma₂)
          where
            ι : ((s₁ ++ list-repeat k s₂ ++ s₃) ++ l₂)
                 (s₁ ++ list-repeat k s₂ ++ s₃ ++ l₂)
            ι = (s₁ ++ list-repeat k s₂ ++ s₃) ++ l₂
                ≡⟨ ++-assoc s₁ (list-repeat k s₂ ++ s₃) l₂ 
                s₁ ++ (list-repeat k s₂ ++ s₃) ++ l₂
                ≡⟨ ap (s₁ ++_) (++-assoc (list-repeat k s₂) s₃ l₂) 
                ((s₁ ++ list-repeat k s₂ ++ s₃ ++ l₂) )
  pumpingLemma .(App re₁ re₂) .(l₁ ++ l₂)
               (MApp l₁ re₁ l₂ re₂ ma₁ ma₂) p | inr u
               with ℕ-split m₂ n₂
    where
      m₂ n₂ : 
      n₂ = length l₂
      m₂ = pumpingConstant re₂
Then we consider the case where length l₂ is greater than or equal to the pumping constant of re₂.
  pumpingLemma .(App re₁ re₂) .(l₁ ++ l₂)
               (MApp l₁ re₁ l₂ re₂ ma₁ ma₂) p | inr u | inl p'
               with pumpingLemma re₂ l₂ ma₂ p'
  pumpingLemma .(App re₁ re₂) .(l₁ ++ l₂)
               (MApp l₁ re₁ l₂ re₂ ma₁ ma₂) p | inr u | inl p' |
               s₁ , s₂ , s₃ , η , η' , η'' , f =
    l₁ ++ s₁ , s₂ , s₃ , τ , η' , κ , g
      where
        m₁ m₂ : 
        m₁ = pumpingConstant re₁
        m₂ = pumpingConstant re₂
        τ : (l₁ ++ l₂)  ((l₁ ++ s₁) ++ s₂ ++ s₃)
        τ = l₁ ++ l₂ ≡⟨ ap (l₁ ++_) η 
            l₁ ++ s₁ ++ s₂ ++ s₃ ≡⟨ ++-assoc l₁ s₁ (s₂ ++ s₃) ⁻¹ 
            (((l₁ ++ s₁) ++ s₂ ++ s₃) )
        κ'' : length ((l₁ ++ s₁) ++ s₂)
             (length l₁ + length (s₁ ++ s₂))
        κ'' = length ((l₁ ++ s₁) ++ s₂)
              ≡⟨ ap length (++-assoc l₁ s₁ s₂) 
              length (l₁ ++ s₁ ++ s₂)
              ≡⟨ length-++ l₁ (s₁ ++ s₂) 
              ((length l₁ + length (s₁ ++ s₂)) )
        κ' : (length l₁ + length (s₁ ++ s₂))  (m₁ + m₂)
        κ' = ≤-+-combine (length l₁) m₁ (length (s₁ ++ s₂)) m₂
             (<-implies-≤ (length l₁) m₁ u) η''
        κ : length ((l₁ ++ s₁) ++ s₂)  (m₁ + m₂)
        κ = transport (_≤ (m₁ + m₂)) (κ'' ⁻¹) κ'
        g : (k : )  ((l₁ ++ s₁) ++ list-repeat k s₂ ++ s₃)
                      =~ App re₁ re₂
        g k = transport (_=~ App re₁ re₂) ι
                        (MApp l₁ re₁ (s₁ ++ list-repeat k s₂ ++ s₃)
                              re₂ ma₁ (f k))
          where
            ι : (l₁ ++ s₁ ++ list-repeat k s₂ ++ s₃)
                 ((l₁ ++ s₁) ++ list-repeat k s₂ ++ s₃)
            ι = l₁ ++ s₁ ++ list-repeat k s₂ ++ s₃
                ≡⟨ ++-assoc l₁ s₁ (list-repeat k s₂ ++ s₃) ⁻¹ 
                (((l₁ ++ s₁) ++ list-repeat k s₂ ++ s₃) )
The last case for App is when both length l₁ and length l₂ are less the pumping constant of re₁ and re₂ respectively, but this contradictions our assumption that length (l₁ ++ l₂) is greater than or equal to the pumping constant of App re₁ re₂.
  pumpingLemma .(App re₁ re₂) .(l₁ ++ l₂)
               (MApp l₁ re₁ l₂ re₂ ma₁ ma₂) p | inr u | inr v =
    !𝟘 (SS-not-≤ (length (l₁ ++ l₂)) τ)
      where
        m₁ m₂ : 
        m₁ = pumpingConstant re₁
        m₂ = pumpingConstant re₂
        τ'' : (S (length l₁) + S (length l₂))
               S (S (length (l₁ ++ l₂)))
        τ'' = S (S (length l₁) + length l₂)
              ≡⟨ ap S (+-S-l (length l₁) (length l₂)) 
              S (S (length l₁ + length l₂))
              ≡⟨ ap (S  S) (length-++ l₁ l₂ ⁻¹) 
              (S (S (length (l₁ ++ l₂))) )
        τ' : S (S (length (l₁ ++ l₂)))  (m₁ + m₂)
        τ' = transport (_≤ (m₁ + m₂)) τ''
                       (≤-+-combine (S (length l₁)) m₁
                                    (S (length l₂)) m₂ u v)
        τ : S (S (length (l₁ ++ l₂)))  length (l₁ ++ l₂)
        τ = ≤-tran (S (S (length (l₁ ++ l₂)))) (m₁ + m₂)
                   (length (l₁ ++ l₂)) τ' p
        SS-not-≤ : (k : )  ¬ (S (S k)  k)
        SS-not-≤ (S k) u = SS-not-≤ k u
Star re also has a few different sub-cases. When after pattern-matching l =~ Star re we have a “head” that is a proof of [] =~ re and tail that is a proof l =~ Star re we just recurse on the tail. This isn’t circular reasoning or create an infinite loop because the “size” of the proof l =~ Star re is decreasing and will bottom out eventually.
  pumpingLemma .(Star re) .l₂ (MStarS [] l₂ re ma₁ ma₂) p =
    pumpingLemma (Star re) l₂ ma₂ p
If after decomposing we we have x :: l₁ =~ re and l₂ =~ Star re we compare the length of x :: l₁ and the pumping constant of re.
  pumpingLemma .(Star re) .((x :: l₁) ++ l₂)
               (MStarS (x :: l₁) l₂ re ma₁ ma₂) p
               with ℕ-split m n₁
    where
      m n₁ : 
      m = pumpingConstant re
      n₁ = length (x :: l₁)
If the length of x :: l₁ is greater than or equal to the pumping constant of re, then we apply the pumping lemma to x :: l₁ =~ re.
  pumpingLemma .(Star re) .((x :: l₁) ++ l₂)
               (MStarS (x :: l₁) l₂ re ma₁ ma₂) p | inl p'
               with pumpingLemma re (x :: l₁) ma₁ p'
  pumpingLemma .(Star re) .((x :: l₁) ++ l₂)
               (MStarS (x :: l₁) l₂ re ma₁ ma₂) p | inl p' |
               s₁ , s₂ , s₃ , η , η' , η'' , f =
    s₁ , s₂ , s₃ ++ l₂ , τ , η' , κ , g
      where
        m : 
        m = pumpingConstant re
        τ : ((x :: l₁) ++ l₂)  (s₁ ++ s₂ ++ s₃ ++ l₂)
        τ = (x :: l₁) ++ l₂
            ≡⟨ ap (_++ l₂) η 
            (s₁ ++ s₂ ++ s₃) ++ l₂
            ≡⟨ ++-assoc s₁ (s₂ ++ s₃) l₂ 
            s₁ ++ (s₂ ++ s₃) ++ l₂
            ≡⟨ ap (s₁ ++_) (++-assoc s₂ s₃ l₂)  
            ((s₁ ++ s₂ ++ s₃ ++ l₂) )
        κ : length (s₁ ++ s₂)  max 1 m
        κ = ≤-tran (length (s₁ ++ s₂)) m (max 1 m)
                   η'' (≤-max-r 1 m)
        g : (k : )  (s₁ ++ list-repeat k s₂ ++ s₃ ++ l₂)
                          =~ Star re
        g k = transport (_=~ Star re) ι
                        (MStarS (s₁ ++ list-repeat k s₂ ++ s₃)
                                l₂ re (f k) ma₂)
          where
            ι : ((s₁ ++ list-repeat k s₂ ++ s₃) ++ l₂)
                 (s₁ ++ list-repeat k s₂ ++ s₃ ++ l₂)
            ι = (s₁ ++ list-repeat k s₂ ++ s₃) ++ l₂
                ≡⟨ ++-assoc s₁ (list-repeat k s₂ ++ s₃) l₂ 
                s₁ ++ (list-repeat k s₂ ++ s₃) ++ l₂
                ≡⟨ ap (s₁ ++_) (++-assoc (list-repeat k s₂) s₃ l₂) 
                ((s₁ ++ list-repeat k s₂ ++ s₃ ++ l₂) )
The case where the length of x :: l₁ is less than the pumping constant of re, is actually or base case. In this instance we can construct our proof object directly without calling the pumping lemma on any sub-expression.
  pumpingLemma .(Star re) .((x :: l₁) ++ l₂)
               (MStarS (x :: l₁) l₂ re ma₁ ma₂) p | inr u =
    [] , (x :: l₁) , l₂ , refl _ , η' , η'' , f
      where
        m : 
        m = pumpingConstant re
        η' : (x :: l₁)  []
        η' p = S-≢-Z (length l₁) (ap length p)
        η'' : length ([] ++ (x :: l₁))  max 1 m
        η'' = ≤-tran (S (length l₁)) m (max 1 m)
                     (<-implies-≤ (S (length l₁)) m u)
                                  (≤-max-r 1 m)
        f : (k : )  ([] ++ list-repeat k (x :: l₁) ++ l₂)
                          =~ Star re
        f Z = ma₂
        f (S k) =
          transport (_=~ Star re) ι
                    (MStarS (x :: l₁)
                            ([] ++ list-repeat k (x :: l₁) ++ l₂)
                            re ma₁ (f k))
          where
            ι : ((x :: l₁) ++ list-repeat k (x :: l₁) ++ l₂)
                 (list-repeat (S k) (x :: l₁) ++ l₂)
            ι = ++-assoc (x :: l₁) (list-repeat k (x :: l₁)) l₂ ⁻¹
Finally, we see why we set the pumping constant of Star re to be the maximum of 1 and the pumping constant of re. The pumping lemma shouldn’t apply to the empty string, setting the pumping constant of Star re in the way we did makes the MStarZ re case impossible.
  pumpingLemma .(Star re) .([]) (MStarZ re) p = !𝟘 q
    where
      m : 
      m = pumpingConstant re
      q : 1  0
      q = ≤-tran 1 (max 1 m) 0 (≤-max-l 1 m) p

Well, I’m pleased with us.

Tail Ends

things they probably should have been in previous posts but aren’t
inl-inr-disjoint : {X : Set } {Y : Set ℓ'} {x : X} {y : Y}
                  inl x  inr y
inl-inr-disjoint {} {ℓ'} {X} {Y} {x} {y} p = 𝟙-≢-𝟘 (ap f p)
  where
    f : X  Y  Set
    f (inl x) = 𝟙
    f (inr y) = 𝟘

constant function
const : {X : Set } {Y : Set ℓ'}  Y  X  Y
const y _ = y

not-<-gives-≥

bounded-∀-next

roots, minimal roots , bounded ℕ-search, root gives minimal

include mutually inductive definitions (even and odd)

Next Time (Probably)

Constructive mathematics, h-levels, univalence.