As a further example of structural induction, we consider an example worked out in detail on the domain of full binary trees.
First consider the following GRAIL grammar for full binary trees.
<tree> ::= <leaf>| <node> <leaf> ::= <leaf-sym:symbolic-atom> <node> ::= ( <left-tree:tree> <right-tree:tree> )Example trees from this grammar include:
A (A B) ((A B) C) ((A B) (C D))Note that interior nodes always have two subtrees.
Now consider the following two functions for counting, respectively, the leaves and nodes of a <tree>
leaf-count[T] = [leaf-p[T] --> 1; otherwise --> plus[leaf-count[left-tree[T]]; leaf-count[right-tree[T]]]] node-count[T] = [leaf-p[T] --> 0; otherwise --> plus[1; plus[node-count[left-tree[T]]; node-count[right-tree[T]]]]]These are pretty straightforward functions.
leaf-count[T] = node-count[T] + 1We want to show that this property holds for all trees T.
Whenever we consider a proof by structural induction, it is
based on an inductive definition of the data domain. In
this case, the data domain is defined by the GRAIL grammar
above. Objects of the domain are generated by the
functions make-leaf
and make-node
.
From the recursive structure of the grammar, we get the
following inductive definition.
x
,
make-leaf[x]
is a binary tree.
t1
and
t2
,
make-node[t1; t2]
is a binary tree.
Whenever we have an inductive definition of a data domain, we can define an analagous proof procedure. Following the approach previously illustrated for algebraic expressions and lists, we develop the proof procedure for binary trees.
To prove a property P(T
) for any binary
tree T
, proceed as follows.
make-leaf[x]
) is true
for any symbolic atom x
.
t1
) and
P(t2
) are true for arbitrary binary trees
t1
and t2
.
Show that P(make-node[t1; t2]
) is true.
Whenever proofs about the objects of an ADT are generated,
those proofs typically use semantic axioms of the
data domain. The semantic axioms describe the
relationships between selector, recognizer, constructor and
converter functions. In particular, since the domain
is inductively defined in terms of the functions that
generate domain objects (make-leaf
and make-node
),
the semantics of the other functions on the domain must be
specified in terms of these ones.
Here are the axioms.
leaf-p[make-leaf[x]] = T
leaf-p[make-node[x]] = F
node-p[make-leaf[x]] = F
node-p[make-node[x]] = T
right-tree[make-node[t1; t2]] = t1
left-tree[make-node[t1; t2]] = t2
leaf-sym[make-leaf[x]] = x
leaf-count[T] = node-count[T] + 1
leaf-count[make-leaf[x]] = node-count[make-leaf[x]] + 1
is true
for any symbolic atom x
. Strategy: proceed
by symbolic evaluation of both sides.
leaf-count[make-leaf[x]] | |
= [leaf-p[make-leaf[x]] --> 1; otherwise --> plus[leaf-count[left-tree[make-leaf[x]]]; leaf-count[right-tree[make-leaf[x]]]]] | Symbolic evaluation
of leaf-count .
|
= 1 | Application of the axiom leaf-p[make-leaf[x]] = T .
|
node-count[make-leaf[x]] + 1 | |
= [leaf-p[make-leaf[x]] --> 0; otherwise --> plus[1; plus[node-count[left-tree[make-leaf[x]]]; node-count[right-tree[make-leaf[x]]]]]] | Symbolic evaluation
of node-count .
|
= 0 + 1 | Application of the axiom leaf-p[make-leaf[x]] = T .
|
= 1 | Addition. |
The two sides have been shown to be equal by symbolic evaluation. Note that we have shown the symbolic evaluation steps in full detail, by copying over conditional expressions.
The base step is complete.
leaf-count[t1] = node-count[t1] + 1
and
leaf-count[t2] = node-count[t2] + 1
are true for arbitrary binary trees.
Show that
leaf-count[make-node[t1; t2]] = node-count[make-node[t1; t2]] + 1
is true.
Again, we proceed by symbolic evaluation of both sides.
leaf-count[make-node[t1; t2]] | |
= [leaf-p[make-node[t1; t2]]] --> 1; otherwise --> plus[leaf-count[left-tree[make-node[t1; t2]]]; leaf-count[right-tree[make-node[t1; t2]]]]] | Symbolic evaluation
of leaf-count .
|
= plus[leaf-count[left-tree[make-node[t1; t2]]; leaf-count[right-tree[make-node[t1; t2]]]] | Application of the axiom leaf-p[make-node[t1; t2]]] = F .
|
= plus[leaf-count[t1]; leaf-count[t2]] | Application of the axioms left-tree[make-node[t1; t2]] = t1
and right-tree[make-node[t1; t2]] = t2 .
|
= leaf-count[t1] + leaf-count[t2] | Definition of plus .
|
= node-count[t1] + 1 + node-count[t2] + 1 | Inductive hypothesis. |
node-count[make-node[t1; t2]] + 1 | |
= plus[1; plus[node-count [left-tree[make-node[t1; t2]]]]; node-count [right-tree[make-node[t1; t2]]]]]]] | Symbolic evaluation
of node-count and application of the axiom leaf-p[make-node[t1; t2]] = F .
|
= plus[1; plus[node-count[t1]; node-count[t2]] | Application of the axioms left-tree[make-node[t1; t2]] = t1
and right-tree[make-node[t1; t2]] = t2 .
|
= node-count[t1] + node-count[t2] + 1 | Definition of plus .
|
leaf-count[make-node[t1; t2]] = node-count[make-node[t1; t2]] + 1is true.
This example shows the mechanics of proofs by structural induction for recursive functional programs in all their gory detail. The main point is to convince you that such proofs of properties of programs can be done. Once you understand the method, you can have much greater confidence in your ability to program and analyze recursions. You will also have a basis for verbally discussing the correctness of programs.