Q:

Proving the fusion law for unfold

I was reading Jeremy Gibbons' article on origami programming and I got stuck on exercise 3.7, which asks the reader to prove the fusion law for list unfolds:

unfoldL p f g .  h = unfoldL p' f' g'

if
p .  h = p'
f .  h = f'
g .  h = h .  g'

The function unfoldL, unfold for lists, is defined as follows:
unfoldL :: (b -> Bool) -> (b -> a) -> (b -> b) -> b -> List a
unfoldL p f g b = if p b then Nil else Cons (f b) (unfoldL p f g (g b))

Here is my current attempt at a proof:
(unfoldL p f g .  h) b
=   { composition }
unfoldL p f g (h b)
=   { unfoldL }
if p (h b) then Nil else Cons (f (h b)) (unfoldL p f g (g (h b)))
=   { composition }
if (p .  h) b then Nil else Cons ((f .  h) b) (unfoldL p f g ((g .  h) b))
=   { assumptions }
if p' b then Nil else Cons (f' b) (unfoldL p f g ((h .  g') b))
=   { composition }
if p' b then Nil else Cons (f' b) ((unfoldL p f g .  h) (g' b))
=   { ??? } 
if p' b then Nil else Cons (f' b) (unfoldL p' f' g' (g' b))
=   { unFoldL }
unFoldL p' f' g'

I am not sure how to justify the step marked with ???.  I should probably use some sort of induction on function application on b?  Related question: what are some good resources that explain and motivate various induction techniques, such as structural induction? 

A:

I haven't read Gibbons's article, and there might be other techniques that he's relying on, but here's what I know. 
It's a good intuition that you are looking for some kind of induction, because what you need is very similar to what you are trying to prove.  But you actually need coinduction here.   It's because unfoldL can produce infinite lists.   In formal type systems, it's very rare that you can prove that two infinite structures are "equal" -- formal equality is much too strong to prove most results.   Instead, we prove bisimilarity, which in informal circumstances might as well be equality. 
Bisimilarity is tricky theoretically, and I won't go into it (partially because I don't fully understand the underpinnings), but working with it in practice is not too hard.   Basically, to prove that two lists are bisimilar, you prove that they are either both Nil, or that they are both Cons with the same head and their tails are bisimilar, and you may use the coinductive hypothesis when proving the bisimilarity of the tails (but not elsewhere). 
So for you, we prove by coinduction that for all b (because we need to use the coinductive hypothesis for different bs):
(unfoldL p f g .  h) b ~~ unfoldL p' f' g' b

So far we have
(unfoldL p f g .  h) b
= { your reasoning }
if p' b then Nil else Cons (f' b) ((unfoldL p f g .  h) (g' b))

Analyzing by cases on p' b, if p' b is True then
if p' b then Nil else Cons (f' b) ((unfoldL p f g .  h) (g' b))
= { p' b is True }
Nil
~~ { reflexivity }
Nil
= { p' b is True }
if p' b then Nil else Cons (f' b) (unfoldL p' f' g' (g' b))
= { unfoldL }
unfoldL p' f' g'

; if p' b is False then
if p' b then Nil else Cons (f' b) ((unfoldL p f g .  h) (g' b))
= { p' b is False }
Cons (f' b) ((unfoldL p f g .  h) (g' b))
*** ~~ { bisimilarity Cons rule, coinductive hypothesis } ***
Cons (f' b) (unfoldL p' f' g' (g' b))
= { p' b is False }
if p' b then Nil else Cons (f' b) (unfoldL p' f' g' (g' b))
= { unfoldL }

The line marked with *** is the key one.   First, note it's a ~~ instead of an =.   Also, we were allowed to use the coinductive hypothesis only underneath the Cons constructor.    If we were allowed to use it anywhere else, then we could prove that any two lists are bisimilar.