File: proofs.md

package info (click to toggle)
haskell-operational 0.2.3.2-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 132 kB
  • sloc: haskell: 441; sh: 78; makefile: 2
file content (190 lines) | stat: -rw-r--r-- 8,015 bytes parent folder | download
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!