1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
Correctness Proofs
==================
This document collects correctness proofs for the `operational` library.
[tutorial]: http://apfelmus.nfshost.com/articles/operational-monad.html
Monad laws
----------
For reasons of efficiency, the `Program` type is not implemented as a list of instructions as presented in the [The Operational Monad Tutorial][tutorial]. However, this means that we now have to prove that the implementations of `view` and `viewT` *respect the monad laws*.
In particular, we say that two programs
e1, e2 :: Program instr a
are *equivalent*, `e1 ~ e2`, if they can be transformed into each other by applying the monad laws on the constructors. For instance, the expressions
e1 = ((m `Bind` f) `Bind` g
e2 = m `Bind` (\a -> f a `Bind` g)
are equivalent for any expressions `m`, `f` and `g`. Our goal is to show that the `view` functions give the same result for equivalent expressions:
e1 ~ e2 => view e1 = view e2
The `ProgramView` type is equipped with an appropriate equality relation:
(Return a1) = (Return a2) iff a1 = a2
(i1 :>>= k1) = (i2 :>>= k2) iff i1 = i2 and k1 x ~ k2 x for all x
### Normal form: list of instructions
The key observation for the proof is the following: As in the [tutorial][], the `Program` type represents a list of instructions. The representation is redundant for the purpose of efficiency, but different expressions should still correspond to the same list of instructions if they are equivalent. After all, equivalence is just about the associativity of the `Bind` operation. This also means that the first instruction, and hence the result of `view` should be unique for each equivalence class.
For simplicity, let us first focus on the pure `Program` type and postpone the case `ProgramT` for monad transformers later.
We can formalize the intuition above by introducing the following types of *normal form*
data NF instr a where
Return' :: a -> NF instr a
(:>>=') :: instr a -> (a -> NF instr b) -> NF instr b
which is simply the list of instructions from the [tutorial][]. Now, we know that `NF` is a monad
instance Monad (NF instr) where
return = Return'
(Return' a) >>= k = k a
(m :>>=' g) >>= k = m :>>=' (\a -> g a :>>=' k)
In particular, it fulfills the monad laws. (Actually we would have to prove that by using coinduction, but I leave that as an exercise.)
We can now map each `Program` to its normal form
normalize :: Program instr a -> NF instr a
normalize (m `Bind` k) = normalize m >>= normalize k
normalize (Return a) = return a
normalize (Instr i) = i :>>=' return
In particular, note that this function is a morphism and `NF` fulfills the monad laws. Hence, equivalent programs will be mapped to the same normal form, i.e.
e1 ~ e2 => normalize e1 = normalize e2
How does this observation help us? Note that the `view` only uses the monad laws to rewrite a `Program`. Using a somewhat sloppy notation, we express this as
e1 ~ view e1
where we intepret a view `i :>>= k` as the "obvious" `Program` expression `Bind (Instr i) k` where the left argument of the `Bind` constructor is an instruction. Furthermore, we can think of the `ProgramView` type as a head normal form. In other words, applying `normalize` to an expression of the form `view e1` will not change the first instruction, which means
normalize (view e1) = normalize (view e2) => view e1 = view e2
(The requires a coinductive argument for the tail of instructions.)
Taking these three implications together, we see that
e1 ~ e2 => view e1 = view e2
as desired.
### Normal form for monad transformers
A similar technique can be used to show that the monad laws also hold for the monad transformer version `ProgramT`. The key observation here is that the normal form is an *effectful list of instructions*
newtype NFT instr m a = JoinLift (m (NFT' instr m a))
data NFT' instr m a where
Return' :: a -> NFT' instr m a
(:>>=') :: instr a -> (a -> NFT instr m b) -> NFT' instr m b
This is in very close analogy to the "effectful list"
type ListT m a = m (ListT' m a)
data ListT' m a = Nil | Cons a (ListT m a)
For example, if the monad `m` is the state monad, then this type represents a list whose tail depends on the current state.
First, we convince ourselves that the `NFT` type is indeed a monad transformer. The corresponding functions are implemented as
instance Monad m => Monad (NFT instr m) where
return a = JoinLift (return (Return' a))
(JoinLift m) >>= k = JoinLift (m >>= f)
where
f (Return' a) = k a
f (i :>>=' f) = return $ i :>>= (\a -> f a >>= k)
instance MonadTrans (NFT instr) where
lift m = JoinLift (fmap Return' m)
singleton i = JoinLift (return (i :>>=' return))
It is somewhat tedious to check the monad laws and the lifting laws, so we skip this step here.
Having convinced ourselves that the normal form type `NFT` is, in fact, a monad transformer, we can define a morphism
normalize :: ProgramT instr m a -> NFT instr m a
normalize (Lift m) = lift m
normalize (m `Bind` k) = m >>= k
normalize (Instr i) = singleton i
and obtain that equivalent programs are mapped to equal normal forms. Similar to the pure case, normalizing the result of `viewT` will not change the first instruction, and we can conclude that the result `viewT` only depends on the normal form of the argument.
Lifting monads
--------------
The normal forms are also useful for proving that class instances can be lifted from a base monad `m` to the monad `ProgramT instr m`.
### Instructions and control operations
Some monads only feature "algebraic" instructions which have the form
instr :: a1 -> a2 -> ... -> m b
so that the types `a1`, `a2`, etc. of the parameters do not contain the monad `m` again. For example, the state monad has two instructions
get :: State s s
put :: s -> State s ()
of precisely this form. Lifting these kinds of instructions is straightforward, i.e. the `ProgramT instr State` monad is also a state monad.
instance (MonadState s m) => MonadState s (ProgramT instr m) where
get = lift get
put = lift . put
However, some monads feature *control operations*, which are instructions that contain the monad `m` in the argument. Essentially, they can change the *control flow*. For example, the `MonadPlus` class contains an instruction
mplus :: MonadPlus m => m a -> m a -> m a
that combines the control flows of two monadic arguments.
For more on the distinction between algebraic operation and control operation, see also a [discussion by Conor McBride][conor].
[conor]: http://www.haskell.org/pipermail/haskell-cafe/2010-April/076185.html
### MonadReader
The main feature of the `MonadReader` class is an algebraic operation
ask :: MonadReader m r => m r
but unfortunately, it also includes a control operation
local :: MonadReader m r => (r -> r) -> m r -> m r
and it is not clear whether this can be lifted to the `ProgramT` transformer. We certainly expect the following law to hold
local r (lift m) = lift (local f m)
Fortunately, this control operation is very benign, in that it is actually a monad morphism
local r (return a) = return a
local r (m >>= k) = local r m >>= local r . k
Imposing that the lifted control operation should also be a morphism, we can define it for normal forms as follows
local :: MonadReader m r => (r -> r)
-> ProgramT instr m a -> ProgramT instr m a
local r (JoinLift m) = JoinLift $ local r (m >>= return . f)
where
f (Return' a) = return a
f (i :>>=' k) = singleton i >>= local r . k
Again, it is somewhat tedious to check that this definition fulfills the lifting and morphism laws. However, we have now succeeded in lifting a control operation!
|