\subsection{Views} \label{Views}
As we have seen pattern matching in Agda can reveal information not only
about the term being matched but also about terms occurring in the type of
this term. For instance, matching a proof of \verb!x == y! against the
\verb!refl! constructor we (and the type checker) will learn that \verb!x!
and \verb!y! are the same.
We can exploit this, and design datatypes whose sole purpose is to tell us
something interesting about its indices. We call such a datatype a {\em
view}~\cite{mcbride:left}. To use the view we define a view function, computing
an element of the view for arbitrary indices.
This section on views is defined in the file {\tt Views.lagda} so here is
the top-level module declaration:
\begin{code}
module Views where
\end{code}
\subsubsection{Natural number parity}
Let us start with an example. We all know that any natural number $n$ can
be written on the form $2k$ or $2k + 1$ for some $k$. Here is a view
datatype expressing that. We use the natural numbers defined in the summer
school library module \verb!Data.Nat!.
\begin{code}
open import Data.Nat
data Parity : Nat -> Set where
even : (k : Nat) -> Parity (k * 2)
odd : (k : Nat) -> Parity (1 + k * 2)
\end{code}
An element of \verb!Parity n! tells you if \verb!n! is even or odd, i.e. if
$n = 2k$ or $n = 2k + 1$, and in each case what $k$ is. The reason for
writing \verb!k * 2! and \verb!1 + k * 2! rather than \verb!2 * k! and
\verb!2 * k + 1! has to do with the fact that \verb!_+_! and \verb!_*_! are
defined by recursion over their first argument. This way around we get a
better reduction behaviour.
Now, just defining the view datatype isn't very helpful. We also need to
show that any natural number can be viewed in this way. In other words,
that given an arbitrary natural number \verb!n! we can to compute an element of
\verb!Parity n!.
\begin{code}
parity : (n : Nat) -> Parity n
parity zero = even zero
parity (suc n) with parity n
parity (suc .(k * 2)) | even k = odd k
parity (suc .(1 + k * 2)) | odd k = even (suc k)
\end{code}
In the \verb!suc n! case we use the view recursively to find out the parity
of \verb!n!. If \verb!n = k * 2! then \verb!suc n = 1 + k * 2! and if
\verb!n = 1 + k * 2! then \verb!suc n = suc k * 2!.
In effect, this view gives us the ability to pattern match on a natural
number with the patterns \verb!k * 2! and \verb!1 + k * 2!. Using this
ability, defining the function that divides a natural number by two is more
or less trivial:
\begin{code}
half : Nat -> Nat
half n with parity n
half .(k * 2) | even k = k
half .(1 + k * 2) | odd k = k
\end{code}
Note that \verb!k! is bound in the pattern for the view, not in the dotted
pattern for the natural number.
\subsubsection{Finding an element in a list}
Let us turn our attention to lists. First some imports: we will use the
definitions of lists and booleans from the summer school library.
\begin{code}
open import Data.Function
open import Data.List
open import Data.Bool
\end{code}
Now, given a predicate \verb!P! and a list \verb!xs! we can define what it
means for \verb!P! to hold for all elements of \verb!xs!:
\begin{code}
infixr 30 _:all:_
data All {A : Set}(P : A -> Set) : List A -> Set where
all[] : All P []
_:all:_ : forall {x xs} -> P x -> All P xs -> All P (x :: xs)
\end{code}
A proof of \verb!All P xs! is simply a list of proofs of \verb!P x! for
each element \verb!x! of \verb!xs!. Note that \verb!P! does not have to be
a decidable predicate. To turn a decidable predicate into a general
predicate we define a function \verb!satisfies!.
\begin{fakecode}
satisfies : {A : Set} -> (A -> Bool) -> A -> Set
satisfies p x = isTrue (p x)
\end{fakecode}
Using the \verb!All! datatype we could prove the second part of the correctness of the
\verb!filter! function, namely that all the elements of the result of
\verb!filter! satisfies the predicate: \verb!All (Sat p) (filter p xs)!.
This is left as an exercise. Instead, let us define some interesting views
on lists.
Given a decidable predicate on the elements of a list, we can either find
an element in the list that satisfies the predicate, or else all elements
satifies the negation of the predicate. Here is the corresponding view
datatype:
\begin{code}
data Find {A : Set}(p : A -> Bool) : List A -> Set where
found : (xs : List A)(y : A) -> satisfies p y -> (ys : List A) ->
Find p (xs ++ y :: ys)
not-found : forall {xs} -> All (satisfies (not ∘ p)) xs ->
Find p xs
\end{code}
We don't specify which element to use as a witness in the \verb!found!
case. If we wanted the view to always return the first (or last) matching
element we could force the elements of \verb!xs! (or \verb!ys!) to satisfy
the negation of \verb!p!. To complete the view we need to define the view
function computing an element of \verb!Find p xs! for any \verb!p! and
\verb!xs!. Here is a first attempt:
\begin{code}
find₁ : {A : Set}(p : A -> Bool)(xs : List A) -> Find p xs
find₁ p [] = not-found all[]
find₁ p (x :: xs) with p x
... | true = found [] x {! !} xs
... | false = {! !}
\end{code}
In the case where \verb!p x! is \verb!true! we want to return \verb!found!
(hence, returning the first match), but there is a problem. The type of the
hole (\verb+{+\verb+! !+\verb+}+) is \verb!isTrue (p x)!, even though we already matched
on \verb!p x! and found out that it was \verb!true!. The problem is that
when we abstracted over \verb!p x! we didn't know that we wanted to use the
\verb!found! constructor, so there were no \verb!p x! to abstract
over. Remember that \verb!with! doesn't remember the connection between the
with-term and the patterns. One solution to this problem is to make this
connection explicit with a proof object. The idea is to not abstract over
the term itself but rather over an arbitrary term of the same type and a
proof that it is equal to the original term. Remember the type of equality
proofs:
\begin{code}
data _==_ {A : Set}(x : A) : A -> Set where
refl : x == x
\end{code}
Now we define the type of elements of a type \verb!A! together with proofs
that they are equal to some given \verb!x! in \verb!A!.
%
\begin{code}
data Inspect {A : Set}(x : A) : Set where
it : (y : A) -> x == y -> Inspect x
\end{code}
%
There is one obvious way to construct an element of \verb!Inspect x!,
namely to pick \verb!x! as the thing which is equal to \verb!x!.
%
\begin{code}
inspect : {A : Set}(x : A) -> Inspect x
inspect x = it x refl
\end{code}
We can now define \verb!find! by abstracting over \verb!inspect (p x)!
rather than \verb!p x! itself. This will provide us with either a proof
of \verb!p x == true! or a proof of \verb!p x == false! which we can use in the arguments
to \verb!found! and \verb!not-found!. First we need a couple of lemmas
about \verb!isTrue! and \verb!isFalse!:
\begin{code}
trueIsTrue : {x : Bool} -> x == true -> isTrue x
trueIsTrue refl = _
falseIsFalse : {x : Bool} -> x == false -> isFalse x
falseIsFalse refl = _
\end{code}
Now we can define \verb!find! without any problems.
\begin{code}
find : {A : Set}(p : A -> Bool)(xs : List A) -> Find p xs
find p [] = not-found all[]
find p (x :: xs) with inspect (p x)
... | it true prf = found [] x (trueIsTrue prf) xs
... | it false prf with find p xs
find p (x :: ._) | it false _ | found xs y py ys =
found (x :: xs) y py ys
find p (x :: xs) | it false prf | not-found npxs =
not-found (falseIsFalse prf :all: npxs)
\end{code}
In the case where \verb!p x! is true, \verb!inspect (p x)! matches
\verb!it true prf! where \verb!prf : p x == true!. Using our lemma we can
turn this into the proof of \verb!isTrue (p x)! that we need for the third
argument of \verb!found!. We get a similar situation when \verb!p x! is
false and \verb!find p xs! returns \verb!not-found!.
\subsubsection{Indexing into a list}
In Sections \ref{Families} and Section \ref{CurryHoward} we saw two ways of
safely indexing into a list. In both cases the type system guaranteed that
the index didn't point outside the list. However, sometimes we have no
control over the value of the index and it might well be that it is
pointing outside the list. One solution in this case would be to wrap the
result of the lookup function in a maybe type, but maybe types don't really
tell you anything very interesting and we can do a lot better. First let us
define the type of proofs that an element \verb!x! is in a list \verb!xs!.
\begin{code}
data _∈_ {A : Set}(x : A) : List A -> Set where
hd : forall {xs} -> x ∈ x :: xs
tl : forall {y xs} -> x ∈ xs -> x ∈ y :: xs
\end{code}
The first element of a list is a member of the list, and any element of the
tail of a list is also an element of the entire list. Given a proof of
\verb!x ∈ xs! we can compute the index at which \verb!x! occurs in
\verb!xs! simply by counting the number of \verb!tl!s in the proof.
\begin{code}
index : forall {A}{x : A}{xs} -> x ∈ xs -> Nat
index hd = zero
index (tl p) = suc (index p)
\end{code}
Now, let us define a view on natural numbers \verb!n! with respect to a
list \verb!xs!. Either \verb!n! indexes some \verb!x! in \verb!xs! in which
case it is of the form \verb!index p! for some proof \verb!p : x ∈ xs!, or
\verb!n! points outside the list, in which case it is of the form
\verb!length xs + m! for some \verb!m!.
\begin{code}
data Lookup {A : Set}(xs : List A) : Nat -> Set where
inside : (x : A)(p : x ∈ xs) -> Lookup xs (index p)
outside : (m : Nat) -> Lookup xs (length xs + m)
\end{code}
In the case that \verb!n! is a valid index we not only get the element at
the corresponding position in \verb!xs! but we are guaranteed that this is
the element that is returned. There is no way a lookup function could cheat
and always return the first element, say.
%
In the case that \verb!n! is indexing outside the list we also get some
more information. We get a proof that \verb!n! is out of bounds and we also
get to know by how much.
Defining the lookup function is no more difficult than it would have been
to define the lookup function returning a maybe.
\begin{code}
_!_ : {A : Set}(xs : List A)(n : Nat) -> Lookup xs n
[] ! n = outside n
(x :: xs) ! zero = inside x hd
(x :: xs) ! suc n with xs ! n
(x :: xs) ! suc .(index p) | inside y p = inside y (tl p)
(x :: xs) ! suc .(length xs + n) | outside n = outside n
\end{code}
\subsubsection{A type checker for $\lambda$-calculus}
To conclude this section on views, let us look at a somewhat bigger
example: a type checker for simply typed $λ$-calculus. This example is due
to Conor McBride~\cite{mcbride:left} and was first implemented in Epigram. His
version not only guaranteed that when the type checker said ok things were
really ok, but also provided a detailed explanation in the case where type
checking failed. We will focus on the positive side here and leave the
reporting of sensible and guaranteed precise error message as an exercise.
First, let us define the type language. We have one base type \verb!ı! and
a function type.
\begin{code}
infixr 30 _→_
data Type : Set where
ı : Type
_→_ : Type -> Type -> Type
\end{code}
When doing type checking we will inevitably have to compare types for
equality, so let us define a view.
\begin{code}
data Equal? : Type -> Type -> Set where
yes : forall {τ} -> Equal? τ τ
no : forall {σ τ} -> Equal? σ τ
_=?=_ : (σ τ : Type) -> Equal? σ τ
ı =?= ı = yes
ı =?= (_ → _) = no
(_ → _) =?= ı = no
(σ₁ → τ₁) =?= (σ₂ → τ₂) with σ₁ =?= σ₂ | τ₁ =?= τ₂
(σ → τ) =?= (.σ → .τ) | yes | yes = yes
(σ₁ → τ₁) =?= (σ₂ → τ₂) | _ | _ = no
\end{code}
Note that we don't give any justification in the \verb!no! case. The
\verb!_=?=_! could return \verb!no! all the time without complaints from
the type checker. In the \verb!yes! case, however, we guarantee that the
two types are identical.
Next up we define the type of raw lambda terms. We use unchecked deBruijn
indices to represent variables.
\begin{code}
infixl 80 _$_
data Raw : Set where
var : Nat -> Raw
_$_ : Raw -> Raw -> Raw
lam : Type -> Raw -> Raw
\end{code}
We use Church style terms in order to simplify type inference. The idea
with our type checker is that it should take a raw term and return a
well-typed term, so we need to define the type of well-typed $λ$-terms with
respect to a context $Γ$ and a type $τ$.
\begin{code}
Cxt = List Type
data Term (Γ : Cxt) : Type -> Set where
var : forall {τ} -> τ ∈ Γ -> Term Γ τ
_$_ : forall {σ τ} -> Term Γ (σ → τ) -> Term Γ σ -> Term Γ τ
lam : forall σ {τ} -> Term (σ :: Γ) τ -> Term Γ (σ → τ)
\end{code}
We represent variables by proofs that a type is in the context. Remember
that the proofs of list membership provide us with an index into the list
where the element can be found. Given a well-typed term we can erase all
the type information and get a raw term.
\begin{code}
erase : forall {Γ τ} -> Term Γ τ -> Raw
erase (var x) = var (index x)
erase (t $ u) = erase t $ erase u
erase (lam σ t) = lam σ (erase t)
\end{code}
In the variable case we turn the proof into a natural number using the
\verb!index! function.
Now we are ready to define the view of a raw term as either being the
erasure of a well-typed term or not. Again, we don't provide any
justification for giving a negative result. Since, we are doing type
inference the type is not a parameter of the view but computed by the view
function.
\begin{code}
data Infer (Γ : Cxt) : Raw -> Set where
ok : (τ : Type)(t : Term Γ τ) -> Infer Γ (erase t)
bad : {e : Raw} -> Infer Γ e
\end{code}
The view function is the type inference function taking a raw term and
computing an element of the \verb!Infer! view.
\begin{code}
infer : (Γ : Cxt)(e : Raw) -> Infer Γ e
\end{code}
Let us walk through the three cases: variable, application, and lambda abstraction.
\begin{code}
infer Γ (var n) with Γ ! n
infer Γ (var .(length Γ + n)) | outside n = bad
infer Γ (var .(index x)) | inside σ x = ok σ (var x)
\end{code}
In the variable case we need to take case of the fact that the raw variable
might be out of scope. We can use the lookup function \verb+_!_+ we defined
above for that. When the variable is in scope the lookup function provides
us with the type of the variable and the proof that it is in scope.
\begin{code}
infer Γ (e₁ $ e₂)
with infer Γ e₁
infer Γ (e₁ $ e₂) | bad = bad
infer Γ (.(erase t₁) $ e₂) | ok ı t₁ = bad
infer Γ (.(erase t₁) $ e₂) | ok (σ → τ) t₁
with infer Γ e₂
infer Γ (.(erase t₁) $ e₂) | ok (σ → τ) t₁ | bad = bad
infer Γ (.(erase t₁) $ .(erase t₂)) | ok (σ → τ) t₁ | ok σ' t₂
with σ =?= σ'
infer Γ (.(erase t₁) $ .(erase t₂))
| ok (σ → τ) t₁ | ok .σ t₂ | yes = ok τ (t₁ $ t₂)
infer Γ (.(erase t₁) $ .(erase t₂))
| ok (σ → τ) t₁ | ok σ' t₂ | no = bad
\end{code}
The application case is the bulkiest simply because there are a lot of
things we need to check: that the two terms are type correct, that the
first term has a function type and that the type of the second term matches
the argument type of the first term. This is all done by pattern matching
on recursive calls to the \verb!infer! view and the type equality view.
\begin{code}
infer Γ (lam σ e) with infer (σ :: Γ) e
infer Γ (lam σ .(erase t)) | ok τ t = ok (σ → τ) (lam σ t)
infer Γ (lam σ e) | bad = bad
\end{code}
Finally, the lambda case is very simple. If the body of the lambda is type
correct in the extended context, then the lambda is well-typed with the
corresponding function type.
Without much effort we have defined a type checker for simply typed
$λ$-calculus that not only is guaranteed to compute well-typed terms, but
also guarantees that the erasure of the well-typed term is the term you
started with.