Theory Types_and_funs

(*<*)
theory Types_and_funs
imports Main
begin
(*>*)
text‹
\vspace{-5ex}
\section{Type and Function Definitions}

Type synonyms are abbreviations for existing types, for example
\index{string@string›}›

type_synonym string = "char list"

text‹
Type synonyms are expanded after parsing and are not present in internal representation and output. They are mere conveniences for the reader.

\subsection{Datatypes}
\label{sec:datatypes}

The general form of a datatype definition looks like this:
\begin{quote}
\begin{tabular}{@ {}rclcll}
\indexed{\isacom{datatype}}{datatype} ('a1,…,'an)t›
     & = & $C_1 \ "›\tau_{1,1}"› \dots "›\tau_{1,n_1}"›$ \\
     & $|$ & \dots \\
     & $|$ & $C_k \ "›\tau_{k,1}"› \dots "›\tau_{k,n_k}"›$
\end{tabular}
\end{quote}
It introduces the constructors \
$C_i :: \tau_{i,1}\Rightarrow \cdots \Rightarrow \tau_{i,n_i} \Rightarrow$~('a1,…,'an)t› \ and expresses that any value of this type is built from these constructors in a unique manner. Uniqueness is implied by the following
properties of the constructors:
\begin{itemize}
\item \emph{Distinctness:} $C_i\ \ldots \neq C_j\ \dots$ \quad if $i \neq j$
\item \emph{Injectivity:}
\begin{tabular}[t]{l}
 $(C_i \ x_1 \dots x_{n_i} = C_i \ y_1 \dots y_{n_i}) =$\\
 $(x_1 = y_1 \land \dots \land x_{n_i} = y_{n_i})$
\end{tabular}
\end{itemize}
The fact that any value of the datatype is built from the constructors implies
the \concept{structural induction}\index{induction} rule: to show
$P~x$ for all $x$ of type ('a1,…,'an)t›,
one needs to show $P(C_i\ x_1 \dots x_{n_i})$ (for each $i$) assuming
$P(x_j)$ for all $j$ where $\tau_{i,j} =$~('a1,…,'an)t›.
Distinctness and injectivity are applied automatically by auto›
and other proof methods. Induction must be applied explicitly.

Like in functional programming languages, datatype values can be taken apart
with case expressions\index{case expression}\index{case expression@case ... of›}, for example
\begin{quote}
\noquotes{@{term[source] "(case xs of []  0 | x # _  Suc x)"}}
\end{quote}
Case expressions must be enclosed in parentheses.

As an example of a datatype beyond typnat and list›, consider binary trees:
›

datatype 'a tree = Tip | Node  "'a tree"  'a  "'a tree"

text‹with a mirror function:›

fun mirror :: "'a tree  'a tree" where
"mirror Tip = Tip" |
"mirror (Node l a r) = Node (mirror r) a (mirror l)"

text‹The following lemma illustrates induction:›

lemma "mirror(mirror t) = t"
apply(induction t)

txt‹yields
@{subgoals[display]}
The induction step contains two induction hypotheses, one for each subtree.
An application of auto› finishes the proof.

A very simple but also very useful datatype is the predefined
@{datatype[display] option}\index{option@option›}\index{None@constNone}\index{Some@constSome}
Its sole purpose is to add a new element constNone to an existing
type typ'a. To make sure that constNone is distinct from all the
elements of typ'a, you wrap them up in constSome and call
the new type typ'a option. A typical application is a lookup function
on a list of key-value pairs, often called an association list:
›
(*<*)
apply auto
done
(*>*)
fun lookup :: "('a * 'b) list  'a  'b option" where
"lookup [] x = None" |
"lookup ((a,b) # ps) x = (if a = x then Some b else lookup ps x)"

text‹
Note that τ1 * τ2 is the type of pairs, also written τ1 × τ2.
Pairs can be taken apart either by pattern matching (as above) or with the
projection functions constfst and constsnd: @{thm fst_conv[of x y]} and @{thm snd_conv[of x y]}.
Tuples are simulated by pairs nested to the right: term(a,b,c)
is short for (a, (b, c))› and τ1 × τ2 × τ3 is short for
τ1 × (τ2 × τ3)›.

\subsection{Definitions}

Non-recursive functions can be defined as in the following example:
\index{definition@\isacom{definition}}›

definition sq :: "nat  nat" where
"sq n = n * n"

text‹Such definitions do not allow pattern matching but only
f x1 … xn = t›, where f› does not occur in t›.

\subsection{Abbreviations}

Abbreviations are similar to definitions:
\index{abbreviation@\isacom{abbreviation}}›

abbreviation sq' :: "nat  nat" where
"sq' n  n * n"

text‹The key difference is that constsq' is only syntactic sugar:
after parsing, termsq' t is replaced by \mbox{termt*t};
before printing, every occurrence of termu*u is replaced by
\mbox{termsq' u}.  Internally, constsq' does not exist.
This is the
advantage of abbreviations over definitions: definitions need to be expanded
explicitly (\autoref{sec:rewr-defs}) whereas abbreviations are already
expanded upon parsing. However, abbreviations should be introduced sparingly:
if abused, they can lead to a confusing discrepancy between the internal and
external view of a term.

The ASCII representation of ≡› is \texttt{==} or \xsymbol{equiv}.

\subsection{Recursive Functions}
\label{sec:recursive-funs}

Recursive functions are defined with \indexed{\isacom{fun}}{fun} by pattern matching
over datatype constructors. The order of equations matters, as in
functional programming languages. However, all HOL functions must be
total. This simplifies the logic --- terms are always defined --- but means
that recursive functions must terminate. Otherwise one could define a
function propf n = f n + (1::nat) and conclude \mbox{prop(0::nat) = 1} by
subtracting termf n on both sides.

Isabelle's automatic termination checker requires that the arguments of
recursive calls on the right-hand side must be strictly smaller than the
arguments on the left-hand side. In the simplest case, this means that one
fixed argument position decreases in size with each recursive call. The size
is measured as the number of constructors (excluding 0-ary ones, e.g., Nil›). Lexicographic combinations are also recognized. In more complicated
situations, the user may have to prove termination by hand. For details
see~citeKrauss.

Functions defined with \isacom{fun} come with their own induction schema
that mirrors the recursion schema and is derived from the termination
order. For example,
›

fun div2 :: "nat  nat" where
"div2 0 = 0" |
"div2 (Suc 0) = 0" |
"div2 (Suc(Suc n)) = Suc(div2 n)"

text‹does not just define constdiv2 but also proves a
customized induction rule:
\[
\inferrule{
\mbox{@{thm (prem 1) div2.induct}}\\
\mbox{@{thm (prem 2) div2.induct}}\\
\mbox{@{thm (prem 3) div2.induct}}}
{\mbox{@{thm (concl) div2.induct[of _ "m"]}}}
\]
This customized induction rule can simplify inductive proofs. For example,
›

lemma "div2 n = n div 2"
apply(induction n rule: div2.induct)

txt‹(where the infix div› is the predefined division operation)
yields the subgoals
@{subgoals[display,margin=65]}
An application of auto› finishes the proof.
Had we used ordinary structural induction on n›,
the proof would have needed an additional
case analysis in the induction step.

This example leads to the following induction heuristic:
\begin{quote}
\emph{Let f› be a recursive function.
If the definition of f› is more complicated
than having one equation for each constructor of some datatype,
then properties of f› are best proved via f.induct›.\index{inductionrule@.induct›}}
\end{quote}

The general case is often called \concept{computation induction},
because the induction follows the (terminating!) computation.
For every defining equation
\begin{quote}
f(e) = … f(r1) … f(rk) …›
\end{quote}
where f(ri)›, i=1…k›, are all the recursive calls,
the induction rule f.induct› contains one premise of the form
\begin{quote}
P(r1) ⟹ … ⟹ P(rk) ⟹ P(e)›
\end{quote}
If f :: τ1 ⇒ … ⇒ τn ⇒ τ› then f.induct› is applied like this:
\begin{quote}
\isacom{apply}(induction x1 … xn rule: f.induct)›\index{inductionrule@induction ... rule:›}
\end{quote}
where typically there is a call f x1 … xn in the goal.
But note that the induction rule does not mention f› at all,
except in its name, and is applicable independently of f›.


\subsection*{Exercises}

\begin{exercise}
Starting from the type 'a tree› defined in the text, define
a function contents ::› typ'a tree  'a list
that collects all values in a tree in a list, in any order,
without removing duplicates.
Then define a function sum_tree ::› typnat tree  nat
that sums up all values in a tree of natural numbers
and prove propsum_tree t = sum_list(contents t)
where constsum_list is predefined by the equations
@{thm sum_list.Nil[where 'a=nat]} and
@{thm sum_list.Cons}.
\end{exercise}

\begin{exercise}
Define the two functions
pre_order› and post_order› of type @{typ "'a tree  'a list"}
that traverse a tree and collect all stored values in the respective order in
a list. Prove proppre_order (mirror t) = rev (post_order t).
\end{exercise}

\begin{exercise}
Define a function intersperse ::› typ'a  'a list  'a list
such that intersperse a [x1, ..., xn] = [x1, a, x2, a, ..., a, xn]›.
Now prove that propmap f (intersperse a xs) = intersperse (f a) (map f xs).
\end{exercise}


\section{Induction Heuristics}\index{induction heuristics}

We have already noted that theorems about recursive functions are proved by
induction. In case the function has more than one argument, we have followed
the following heuristic in the proofs about the append function:
\begin{quote}
\emph{Perform induction on argument number $i$\\
 if the function is defined by recursion on argument number $i$.}
\end{quote}
The key heuristic, and the main point of this section, is to
\emph{generalize the goal before induction}.
The reason is simple: if the goal is
too specific, the induction hypothesis is too weak to allow the induction
step to go through. Let us illustrate the idea with an example.

Function constrev has quadratic worst-case running time
because it calls append for each element of the list and
append is linear in its first argument.  A linear time version of
constrev requires an extra argument where the result is accumulated
gradually, using only~#›:
›
(*<*)
apply auto
done
(*>*)
fun itrev :: "'a list  'a list  'a list" where
"itrev []        ys = ys" |
"itrev (x#xs) ys = itrev xs (x#ys)"

text‹The behaviour of constitrev is simple: it reverses
its first argument by stacking its elements onto the second argument,
and it returns that second argument when the first one becomes
empty. Note that constitrev is tail-recursive: it can be
compiled into a loop; no stack is necessary for executing it.

Naturally, we would like to show that constitrev reverses its first argument:
›

lemma "itrev xs [] = rev xs"

txt‹There is no choice as to the induction variable:
›

apply(induction xs)
apply(auto)

txt‹
Unfortunately, this attempt does not prove
the induction step:
@{subgoals[display,margin=70]}
The induction hypothesis is too weak.  The fixed
argument,~term[], prevents it from rewriting the conclusion.  
This example suggests a heuristic:
\begin{quote}
\emph{Generalize goals for induction by replacing constants by variables.}
\end{quote}
Of course one cannot do this naively: propitrev xs ys = rev xs is
just not true.  The correct generalization is
›
(*<*)oops(*>*)
lemma "itrev xs ys = rev xs @ ys"
(*<*)apply(induction xs, auto)(*>*)
txt‹
If ys› is replaced by term[], the right-hand side simplifies to
termrev xs, as required.
In this instance it was easy to guess the right generalization.
Other situations can require a good deal of creativity.  

Although we now have two variables, only xs› is suitable for
induction, and we repeat our proof attempt. Unfortunately, we are still
not there:
@{subgoals[display,margin=65]}
The induction hypothesis is still too weak, but this time it takes no
intuition to generalize: the problem is that the ys›
in the induction hypothesis is fixed,
but the induction hypothesis needs to be applied with
terma # ys instead of ys›. Hence we prove the theorem
for all ys› instead of a fixed one. We can instruct induction
to perform this generalization for us by adding arbitrary: ys›\index{arbitrary@arbitrary:›}.
›
(*<*)oops
lemma "itrev xs ys = rev xs @ ys"
(*>*)
apply(induction xs arbitrary: ys)

txt‹The induction hypothesis in the induction step is now universally quantified over ys›:
@{subgoals[display,margin=65]}
Thus the proof succeeds:
›

apply(auto)
done

text‹
This leads to another heuristic for generalization:
\begin{quote}
\emph{Generalize induction by generalizing all free
variables\\ {\em(except the induction variable itself)}.}
\end{quote}
Generalization is best performed with arbitrary: y1 … yk. 
This heuristic prevents trivial failures like the one above.
However, it should not be applied blindly.
It is not always required, and the additional quantifiers can complicate
matters in some cases. The variables that need to be quantified are typically
those that change in recursive calls.


\subsection*{Exercises}

\begin{exercise}
Write a tail-recursive variant of the add› function on typnat:
termitadd :: nat  nat  nat.
Tail-recursive means that in the recursive case, itadd› needs to call
itself directly: \mbox{termitadd (Suc m) n} = itadd …›.
Prove propitadd m n = add m n.
\end{exercise}


\section{Simplification}

So far we have talked a lot about simplifying terms without explaining the concept.
\conceptidx{Simplification}{simplification} means
\begin{itemize}
\item using equations $l = r$ from left to right (only),
\item as long as possible.
\end{itemize}
To emphasize the directionality, equations that have been given the
simp› attribute are called \conceptidx{simplification rules}{simplification rule}.
Logically, they are still symmetric, but proofs by
simplification use them only in the left-to-right direction.  The proof tool
that performs simplifications is called the \concept{simplifier}. It is the
basis of auto› and other related proof methods.

The idea of simplification is best explained by an example. Given the
simplification rules
\[
\begin{array}{rcl@ {\quad}l}
term0 + n::nat &=›& n› & (1) \\
term(Suc m) + n &=›& termSuc (m + n) & (2) \\
(Suc m ≤ Suc n)› &=›& (m ≤ n)› & (3)\\
(0 ≤ m)› &=›& constTrue & (4)
\end{array}
\]
the formula prop0 + Suc 0  Suc 0 + x is simplified to constTrue
as follows:
\[
\begin{array}{r@ {}c@ {}l@ {\quad}l}
(0 + Suc 0› & \leq & Suc 0 + x)›  & \stackrel{(1)}{=} \\
(Suc 0›     & \leq & Suc 0 + x)›  & \stackrel{(2)}{=} \\
(Suc 0›     & \leq & Suc (0 + x))› & \stackrel{(3)}{=} \\
(0›         & \leq & 0 + x)›      & \stackrel{(4)}{=} \\[1ex]
 & constTrue
\end{array}
\]
Simplification is often also called \concept{rewriting}
and simplification rules \conceptidx{rewrite rules}{rewrite rule}.

\subsection{Simplification Rules}

The attribute simp› declares theorems to be simplification rules,
which the simplifier will use automatically. In addition,
\isacom{datatype} and \isacom{fun} commands implicitly declare some
simplification rules: \isacom{datatype} the distinctness and injectivity
rules, \isacom{fun} the defining equations. Definitions are not declared
as simplification rules automatically! Nearly any theorem can become a
simplification rule. The simplifier will try to transform it into an
equation. For example, the theorem prop¬ P is turned into propP = False.

Only equations that really simplify, like proprev (rev xs) = xs and
propxs @ [] = xs, should be declared as simplification
rules. Equations that may be counterproductive as simplification rules
should only be used in specific proof steps (see \autoref{sec:simp} below).
Distributivity laws, for example, alter the structure of terms
and can produce an exponential blow-up.

\subsection{Conditional Simplification Rules}

Simplification rules can be conditional.  Before applying such a rule, the
simplifier will first try to prove the preconditions, again by
simplification. For example, given the simplification rules
\begin{quote}
propp(0::nat) = True\\
propp(x)  f(x) = g(x),
\end{quote}
the term termf(0::nat) simplifies to termg(0::nat)
but termf(1::nat) does not simplify because propp(1::nat)
is not provable.

\subsection{Termination}

Simplification can run forever, for example if both propf x = g x and
propg(x) = f(x) are simplification rules. It is the user's
responsibility not to include simplification rules that can lead to
nontermination, either on their own or in combination with other
simplification rules. The right-hand side of a simplification rule should
always be ``simpler'' than the left-hand side --- in some sense. But since
termination is undecidable, such a check cannot be automated completely
and Isabelle makes little attempt to detect nontermination.

When conditional simplification rules are applied, their preconditions are
proved first. Hence all preconditions need to be
simpler than the left-hand side of the conclusion. For example
\begin{quote}
propn < m  (n < Suc m) = True
\end{quote}
is suitable as a simplification rule: both propn<m and constTrue
are simpler than \mbox{propn < Suc m}. But
\begin{quote}
propSuc n < m  (n < m) = True
\end{quote}
leads to nontermination: when trying to rewrite propn<m to constTrue
one first has to prove \mbox{propSuc n < m}, which can be rewritten to constTrue provided propSuc(Suc n) < m, \emph{ad infinitum}.

\subsection{The \indexed{simp›}{simp} Proof Method}
\label{sec:simp}

So far we have only used the proof method auto›.  Method simp›
is the key component of auto›, but auto› can do much more. In
some cases, auto› is overeager and modifies the proof state too much.
In such cases the more predictable simp› method should be used.
Given a goal
\begin{quote}
1. ⟦ P1; …; Pm ⟧ ⟹ C›
\end{quote}
the command
\begin{quote}
\isacom{apply}(simp add: th1 … thn)›
\end{quote}
simplifies the assumptions Pi and the conclusion C› using
\begin{itemize}
\item all simplification rules, including the ones coming from \isacom{datatype} and \isacom{fun},
\item the additional lemmas th1 … thn, and
\item the assumptions.
\end{itemize}
In addition to or instead of add› there is also del› for removing
simplification rules temporarily. Both are optional. Method auto›
can be modified similarly:
\begin{quote}
\isacom{apply}(auto simp add: … simp del: …)›
\end{quote}
Here the modifiers are simp add› and simp del›
instead of just add› and del› because auto›
does not just perform simplification.

Note that simp› acts only on subgoal 1, auto› acts on all
subgoals. There is also simp_all›, which applies simp› to
all subgoals.

\subsection{Rewriting with Definitions}
\label{sec:rewr-defs}

Definitions introduced by the command \isacom{definition}
can also be used as simplification rules,
but by default they are not: the simplifier does not expand them
automatically. Definitions are intended for introducing abstract concepts and
not merely as abbreviations. Of course, we need to expand the definition
initially, but once we have proved enough abstract properties of the new
constant, we can forget its original definition. This style makes proofs more
robust: if the definition has to be changed, only the proofs of the abstract
properties will be affected.

The definition of a function f› is a theorem named f_def›
and can be added to a call of simp› like any other theorem:
\begin{quote}
\isacom{apply}(simp add: f_def)›
\end{quote}
In particular, let-expressions can be unfolded by
making @{thm[source] Let_def} a simplification rule.

\subsection{Case Splitting With simp›}

Goals containing if-expressions are automatically split into two cases by
simp› using the rule
\begin{quote}
propP(if A then s else t) = ((A  P(s))  (~A  P(t)))
\end{quote}
For example, simp› can prove
\begin{quote}
prop(A  B) = (if A then B else False)
\end{quote}
because both propA  (A & B) = B and prop~A  (A & B) = False
simplify to constTrue.

We can split case-expressions similarly. For nat› the rule looks like this:
@{prop[display,margin=65,indent=4]"P(case e of 0  a | Suc n  b n) = ((e = 0  P a)  (n. e = Suc n  P(b n)))"}
Case expressions are not split automatically by simp›, but simp›
can be instructed to do so:
\begin{quote}
\isacom{apply}(simp split: nat.split)›
\end{quote}
splits all case-expressions over natural numbers. For an arbitrary
datatype t› it is t.split›\index{split@.split›} instead of @{thm[source] nat.split}.
Method auto› can be modified in exactly the same way.
The modifier \indexed{split:›}{split} can be followed by multiple names.
Splitting if or case-expressions in the assumptions requires 
split: if_splits› or split: t.splits›.

\ifsem\else
\subsection{Rewriting let› and Numerals}

Let-expressions (termlet x = s in t) can be expanded explicitly with the simplification rule
@{thm[source] Let_def}. The simplifier will not expand let›s automatically in many cases.

Numerals of type typnat can be converted to constSuc terms with the simplification rule
@{thm[source] numeral_eq_Suc}. This is required, for example, when a function that is defined
by pattern matching with constSuc is applied to a numeral: if f› is defined by
f 0 = ...› and  f (Suc n) = ...›, the simplifier cannot simplify f 2› unless
2› is converted to termSuc(Suc 0) (via @{thm[source] numeral_eq_Suc}).
\fi

\subsection*{Exercises}

\exercise\label{exe:tree0}
Define a datatype tree0› of binary tree skeletons which do not store
any information, neither in the inner nodes nor in the leaves.
Define a function nodes :: tree0 ⇒ nat› that counts the number of
all nodes (inner nodes and leaves) in such a tree.
Consider the following recursive function:
›
(*<*)
datatype tree0 = Tip | Node tree0 tree0
(*>*)
fun explode :: "nat  tree0  tree0" where
"explode 0 t = t" |
"explode (Suc n) t = explode n (Node t t)"

text ‹
Find an equation expressing the size of a tree after exploding it
(\noquotes{@{term [source] "nodes (explode n t)"}}) as a function
of termnodes t and n›. Prove your equation.
You may use the usual arithmetic operators, including the exponentiation
operator ``^›''. For example, \noquotes{@{prop [source] "2 ^ 2 = 4"}}.

Hint: simplifying with the list of theorems @{thm[source] algebra_simps}
takes care of common algebraic properties of the arithmetic operators.
\endexercise

\exercise
Define arithmetic expressions in one variable over integers (type typint)
as a data type:
›

datatype exp = Var | Const int | Add exp exp | Mult exp exp

text‹
Define a function \noquotes{@{term [source]"eval :: exp  int  int"}}
such that termeval e x evaluates e› at the value
x›.

A polynomial can be represented as a list of coefficients, starting with
the constant. For example, term[4, 2, -1, 3::int] represents the
polynomial $4 + 2x - x^2 + 3x^3$.
Define a function \noquotes{@{term [source] "evalp :: int list  int  int"}}
that evaluates a polynomial at the given value.
Define a function \noquotes{@{term[source] "coeffs :: exp  int list"}}
that transforms an expression into a polynomial. This may require auxiliary
functions. Prove that coeffs› preserves the value of the expression:
\mbox{propevalp (coeffs e) x = eval e x.}
Hint: consider the hint in Exercise~\ref{exe:tree0}.
\endexercise
›
(*<*)
end
(*>*)