sum
and psum
mRTheorem
mRTheorem
on plusWe already saw how to prove properties on integers using induction on natural numbers. Next, we will see how to prove properties on algebraic data types using structural induction.
As expected, we will prove properties of functions that manipulate the most common algebraic data type, lists.
Next, we define and reflect two functions, map
and id
, that we will use to illustrate structural induction on lists.
The first property we will prove is that mapping the identity function over a list is the same as the identity over the list.
This property can be used as an optimization pass, in the right hand side, the list is not traversed by map!
The proof goes by structural induction on the list the list xs
.
xs
is []
, which goes by rewriting.xs
is x:xs
, which goes by rewriting and the induction hypothesis.Note that Liquid Haskell checks both that both cases are defined (by totality) and that the inductive call is well-founded (by termination).
As before, ple
can be used to automate the proof. We essentially need to keep the case splitting and the induction hypothesis.
Question: What is the ple simplified version of the proof?
Just the inductive call! Where the base case is trivial:
{-@ idMap :: xs:[a] -> {map id xs == id xs} @-}
idMap :: [a] -> Proof
idMap [] = ()
idMap (x:xs) = idMap xs
Next, we will prove that mapping two functions over a list is the same as mapping the composition of the functions over the list.
For this we define the composition of two functions.
Question: Let’s prove map fusion.
The proof is the following:
{-@ mapFusion :: f:(b -> c) -> g:(a -> b) -> xs:[a]
-> {map (comp f g) xs == (map f) (map g xs)} @-}
mapFusion :: (b -> c) -> (a -> b) -> [a] -> Proof
mapFusion f g [] = ()
mapFusion f g (x:xs) = mapFusion f g xs
A monoid is a set equipped with a binary operation that is associative and has an identity element. % Lists as monoids! Because they have an associative binary operation (list append) and a neutral element (empty list). Let’s prove the monoid laws for lists.
Monoid structures have three laws: associativity, left identity and right identity. So, let’s prove these laws for lists.
empty
is the left identity for append
.The left identity proof is the following:
emptyLeftIdentity xs = ()
empty
is the right identity for append
.The right identity proof is the following:
emptyRightIdentity [] = ()
emptyRightIdentity (x:xs) = emptyRightIdentity xs
append
is associative.The associativity proof is the following:
appendAssoc [] ys zs = ()
appendAssoc (x:xs) ys zs = appendAssoc xs ys zs
As a final example, let’s define list reversing and prove that reverse distributes over append.
Question: Let’s prove distributivity. Hint, you can use the proved lemmas emptyRightIdentity
and appendAssoc
.
The complete proof is the following:
distributivity :: [a] -> [a] -> ()
{-@ distributivity :: xs:[a] -> ys:[a]
-> { reverse (xs ++ ys) == reverse ys ++ reverse xs } @-}
distributivity [] ys
= reverse ([] ++ ys)
? emptyLeftIdentity ys
=== reverse ys
? emptyRightIdentity (reverse ys)
=== reverse ys ++ []
=== reverse ys ++ reverse []
*** QED
distributivity (x:xs) ys
= reverse ((x:xs) ++ ys)
=== reverse (x:(xs ++ ys))
=== reverse (xs ++ ys) ++ [x]
? distributivity xs ys
=== (reverse ys ++ reverse xs) ++ [x]
? appendAssoc (reverse ys) (reverse xs) [x]
=== reverse ys ++ (reverse xs ++ [x])
=== reverse ys ++ reverse (x:xs)
*** QED
We saw how to use Liquid Haskell to encode proofs of structural induction on lists. We proved the functor and monoid laws for lists and distributivity of reverse over append, using the two Liquid Haskell tactics of ple
and rewriteWith
.