The saga of my quest to understand the State monad
Submitted by metaperl on Wed, 04/26/2006 - 1:55am.
::
Part I:
-- Given this:
fmap :: (a -> b) -> (State s a) -> (State s b)
fmap f (State m) = State (onVal f . m)
where onVal f (x, s) = (f x, s)
-- problem 1: the type signature says that the ultimate return value is
-- (State s b) which would be this function : (\s -> (b,s))
-- However, the type of onVal is this:
-- :t onVal
-- onVal :: (a -> a1) -> (a, b) -> (a1, b)
-- In other words, it is just a 2-tuple. It is not clear that a 2-tuple
-- satisfies the expected type (State s b)
-- problem 2: I don't understand the expression (onVal f . m)
-- is this onval (f . m) or (onVal f) . m
- metaperl's blog
- Login to post comments
Since everyone in #haskell was ignoring me, I went to haskell and monads and tried to understand what he was saying. I believe in stopping the second I can't understand something, so here is what I did not understand:
type StateTrans s a = s -> (s, a)
Let us define two functions. The first is a kind of composition function.
(>>=) :: StateTrans s a ->
(a -> StateTrans s b) ->
StateTrans s b
p >>= k = \s0 -> let (s1, a) = p s0
q = k a
in q s1
Now what I don't get is that
(>>=)promises to returnStateTrans s bbut I think it returns a 2-tuple. Why? Let's tracep >>= kp s0returns a 2-tuple(s1,a)k areturns aStateTrans s b. It returns a function that expects a state and returns a 2-tuple.q s1provides the desired state, causingqto return a 2-tupleThis returned 2-tuple is _not_ a state transition and therefore violates the given type signature.
Look carefully at the right hand side of the equation they give for p >>= k. Right at the start:
\s0 -> .... So already it can't be a pair, but must be some type of function. We're hoping that it's a function of types -> (s,b), so let's say thatWe already know from the type signature of
(>>=)that in this case,So, we know that the return value has type
s -> uwhere
uis the type of:let (s1,a) = p s0 q = k a in q s1Let's unravel that piece by piece, I'll take the smallest steps possible, and document the reasons for things. (The things I didn't give a reason for are things we already know from above.)
So, we've determined that our type
u = (s,b), so the full type of the right hand side iss -> (s,b) = StateTrans s b. So the thing checks out.Formal proofs aside, intuitively, this should also make sense. What is
m >>= kreally doing? Well, it's taking a state computationm, and a function from possible results of that state computation, to other state computations,k(you might even call it a continuation). It's forming a new state computation which, when applied to an initial state of types, is going to perform the first state computation, getting a result of typeaand a *new* state value of types. It then applies that result of typeato the functionk, which gives us back a state computation to continue with. It then applies the new state value to that continuation, getting a final state of typesand a final result of typeb.But none of that happens immediately -- it's all inside the function, and none of it can happen until the actual initial state is passed in.
Now, that's pretty wordy, but it's exactly what the code says it does if you look carefully. However, the following code, which is equivalent, might be easier to read:
newtype State s a = State (s -> (s,a)) runState :: State s a -> s -> (s,a) runState (State f) s = f s instance Monad (State s) where return x = State $ \s -> (s,x) m >>= k = State $ \s -> -- given an initial state let (t, a) = runState m s -- we run the computation m, -- getting a new state and -- a value in runState (k a) t -- then run the resulting future -- given to us by k, passing in -- the new state...but the return value of ">>=" is not the return value of "q". Note the lambda function starting with
\s0 ->... Maybe adding some parenthesis will help...(>>=) :: StateTrans s a -> (a -> StateTrans s b) -> StateTrans s b p >>= k = \s0 -> (let (s1, a) = p s0 q = k a in q s1)It's
(onVal f) . m. (Function application has precedence over all other operators.) Note that since it is a composition of functions, it must be a function itself, and could not possibly be just a pair.In this case, we use
u = s,v = (a,s), andw = (b,s)in the type for(.), sowhich is precisely the desired type.
Thinking about how these functions act, this makes sense: the new state transition is just like the old one, except that afterward, we want to apply f to the value component, which is what onVal f does. Composition handles the 'afterward' bit.
Type-compatibility between the composed functions should have told me how
mandonValwere related: m returns(a, s)therefore I only had to look atm"digest"(a,s)(onVal m)"digest"(a,s)Since
fcan only takeaand not(a,s)we see that it cannot be composed with m.