So, I wanted to manually prove the Composition law for Maybe applicative which is:
u <*> (v <*> w) = pure (.) <*> u <*> v <*> w
I used these steps to prove it:
u <*> (v <*> w) [Left hand side of the law]
= (Just f) <*> (v <*> w) [Assume u ~ Just f]
= fmap f (v <*> w)
= fmap f (Just g <*> w) [Assume v ~ Just g]
= fmap f (fmap g w)
= fmap (f . g) w
pure (.) <*> u <*> v <*> w [Right hand side of the law]
= Just (.) <*> u <*> v <*> w
= fmap (.) u <*> v <*> w
= fmap (.) (Just f) <*> v <*> w [Replacing u with Just f]
= Just (f .) <*> v <*> w
= Just (f .) <*> Just g <*> w [Replacing v with Just g]
= fmap (f .) (Just g) <*> w
= Just (f . g) <*> w
= fmap (f . g) w
Is proving like this correct? What really concerns me is that I assume u
and v
for some functions embedded in Just
data constructor to proceed with my proof. Is that acceptable? Is there any better way to prove this?
Applicative functor expressions are just function applications in the context of some functor. Hence:
We want to prove that:
Consider:
Therefore, the left hand side is:
For
Maybe
we know thatpure = Just
. Hence ifu
,v
andw
areJust
values then we know that the composition law holds.However, what if any one of them is
Nothing
? We know that:Hence if any one of them is
Nothing
then the entire expression becomesNothing
(except in the second case if the first argument isundefined
) and sinceNothing == Nothing
the law still holds.Finally, what about
undefined
(a.k.a. bottom) values? We know that:Hence the following expressions will make the program halt:
However the following expression will result in
Nothing
:In either case the composition law still holds.
You translated the use of
(<*>)
throughfmap
. The other answers also do some pattern matching.Usually you need to open the definition of the functions to reason about them, not just assume what they do. (You assume
(pure f) <*> x
is the same asfmap f x
)For example,
(<*>)
is defined asap
forMaybe
inControl.Applicative
(or can be proven to be equivalent to it for anyMonad
, even if you redefine it), andap
is borrowed fromMonad
, which is defined asliftM2 id
, andliftM2
is defined like so:So, reduce both left- and right-hand sides to see they are equivalent:
Now, the right-hand side:
That's it.
[1] http://www.haskell.org/haskellwiki/Monad_laws
The rules that are generated by the definition of Maybe are
and
Along with
If these are the only rules which result in
Maybe A
then we can always invert them (run from bottom to top) in proofs so long as we're exhaustive. This is argument by case examination of a value of typeMaybe A
.You did two cases analyses, but weren't exhaustive. It might be that
u
orv
are actuallyNothing
or bottom.A useful tool to learn when proving stuff about Haskell code is Agda: Here is a short proof stating what you want to prove:
This illustrates a couple of points others have pointed out: