Martin Escardo. Notes originally written for the module Advanced Functional Programming of the University of Birmingham, UK.

Some functions on lists

Length, concatenation, map and singleton lists

open import List
open import natural-numbers-type

length : {A : Type}  List A  
length []        = 0
length (x :: xs) = 1 + length xs

_++_ : {A : Type}  List A  List A  List A
[]        ++ ys = ys
(x :: xs) ++ ys = x :: (xs ++ ys)

infixr 20 _++_

map : {A B : Type}  (A  B)  List A  List B
map f []        = []
map f (x :: xs) = f x :: map f xs

[_] : {A : Type}  A  List A
[ x ] = x :: []

Properties of list concatenation

[]-left-neutral : {X : Type} (xs : List X)  [] ++ xs  xs
[]-left-neutral = refl

[]-right-neutral : {X : Type} (xs : List X)  xs ++ []  xs
[]-right-neutral []        = refl []
[]-right-neutral (x :: xs) =
   (x :: xs) ++ [] ≡⟨ refl _ 
   x :: (xs ++ []) ≡⟨ ap (x ::_) ([]-right-neutral xs) 
   x :: xs         

++-assoc : {A : Type} (xs ys zs : List A)  (xs ++ ys) ++ zs  xs ++ (ys ++ zs)
++-assoc []        ys zs = refl (ys ++ zs)
++-assoc (x :: xs) ys zs =
   ((x :: xs) ++ ys) ++ zs ≡⟨ refl _ 
   x :: ((xs ++ ys) ++ zs) ≡⟨ ap (x ::_) (++-assoc xs ys zs) 
   x :: (xs ++ (ys ++ zs)) ≡⟨ refl _ 
   (x :: xs) ++ ys ++ zs   
Short proofs:
[]-right-neutral' : {X : Type} (xs : List X)  xs ++ []  xs
[]-right-neutral' []        = refl []
[]-right-neutral' (x :: xs) = ap (x ::_) ([]-right-neutral' xs)

++-assoc' : {A : Type} (xs ys zs : List A)  (xs ++ ys) ++ zs  xs ++ (ys ++ zs)
++-assoc' []        ys zs = refl (ys ++ zs)
++-assoc' (x :: xs) ys zs = ap (x ::_) (++-assoc' xs ys zs)

List reversal

First an obvious, but slow reversal algorithm:
reverse : {A : Type}  List A  List A
reverse []        = []
reverse (x :: xs) = reverse xs ++ [ x ]
This is quadratic time. To get a linear-time algorithm, we use the following auxiliary function:
rev-append : {A : Type}  List A  List A  List A
rev-append []        ys = ys
rev-append (x :: xs) ys = rev-append xs (x :: ys)
The intended behaviour of rev-append is that rev-append xs ys = reverse xs ++ ys. Using this fact, the linear time algorithm is the following:
rev : {A : Type}  List A  List A
rev xs = rev-append xs []
We now want to show that rev and reverse behave in the same way. To do this, we first show that the auxiliary function does behave as intended:
rev-append-behaviour : {A : Type} (xs ys : List A)
                      rev-append xs ys  reverse xs ++ ys
rev-append-behaviour []        ys = refl ys
rev-append-behaviour (x :: xs) ys =
   rev-append (x :: xs) ys     ≡⟨ refl _ 
   rev-append xs (x :: ys)     ≡⟨ rev-append-behaviour xs (x :: ys) 
   reverse xs ++ (x :: ys)     ≡⟨ refl _ 
   reverse xs ++ ([ x ] ++ ys) ≡⟨ sym (++-assoc (reverse xs) [ x ] ys) 
   (reverse xs ++ [ x ]) ++ ys ≡⟨ refl _ 
   reverse (x :: xs) ++ ys     
We state this as follows in Agda:
rev-correct : {A : Type} (xs : List A)  rev xs  reverse xs
rev-correct xs =
   rev xs           ≡⟨ refl _ 
   rev-append xs [] ≡⟨ rev-append-behaviour xs [] 
   reverse xs ++ [] ≡⟨ []-right-neutral (reverse xs) 
   reverse xs       

Go back to the table of contents