I'm a Haskell newbie, and I think I understand monads and their mechanics (at least for the list, state, maybe, writer and reader monads), but I want to understand why they have been defined the way they have, or why they have to be the way they are, to aid in my intuition in thinking about them.
Specifically, what is it about reading that makes the reader or state monads need to be functions (i.e. \s -> (a,s)
), and not just data like the writer monad (i.e. (w,a)
)?
Also, could a writer monad be used as a state monad, where the log is used as a string representation of state, as long as the MonadPlus
functionality is not used? Is the monadic function used with writer monads allowed to look at the current log and modify if at will, or is it only the writer monad's bind function that is allowed to look at the log?
Also, why are monads defined in terms of monadic functions that with type a -> m b
, instead of with type m a -> mb
? What's so natural about a function going from a base type to a monad wrapped type?
Thanks for your answers.
The state and reader monad both depend on the value of whatever state we're interested in. We need to be able to access it otherwise how could we write something like
foo = do
i <- get
return $ if i == 1 then 2 else 3
So our state monad naturally looks like something that takes in a state, does stuff, and produces a new one.
Likewise with the reader monad, we take in some state, magic, and produce an output (but no new state since it's Reader s a <=> s -> a
.
Now this is very different than the writer monad. In the writer, we don't care about what's been stuck in the state previously, all we want to do is take our current state, and jam some more stuff in there. Because our state is a monoid, we have a guaranteed start state mempty
and a way to jam stuff in mappend
. This means that we can "run" each computation in an empty context so to speak and just fuse the outputs together and get identical results.
Ok, now there's about a few questions so I'll go one at a time
If the writer monad had access to the previously written results, then it's going to look like s -> (s, a)
which is the state monad :) So yes, whatever you could do with the state monad could be done with this augmented writer.
In fact both the StateT
and WriterT
monads have MonadPlus
instances so I'm not sure what you're getting at here..
Since there isn't an arbitrary function m a -> a
, we need some way to unwrap a computation to view the results ala >>=
, and since return :: a -> m a
makes it trivial to go the other way, it's more general to go from plain to monadic value. Otherwise we'd just have these useless IO String
, STM Int
and whatnot that we couldn't depend on for the rest of our computation since fmap
won't let us hoist in more side effects.
They're defined differently because they do different things.
Take the reader monad. Start by thinking about what it means, not about how it works.
A computation in the reader monad is one that depends on an extra piece of information, the reader's "environment". So a Reader Env Int
is an Int
that depends on the environment (of type Env
); if I evaluate it with one environment I'll get one Int
value, and if I evaluate it with a different environment I'll get another Int
value. If I don't have an environment I can't know what value the Reader env Int
is.
Now, what kind of value will give me an Int
if I give it an Env
? A function of type Env -> Int
! So that generalises to e -> a
being a monad for each e
(with a
being the type parameter of the monad; (->) e
if you like the prefix notation).
Now lets think about the meaning of the writer monad. A computation in the writer monad produces a value, but it also produces an extra value "on the side": the "log" value. And when we bind together a series of monadic computations from in the writer monad, the log values will be combined (if we require the log type to be a monoid, then this guarantees log values can be combined with no other knowledge about what they are). So a Writer Log Int
is an Int
that also comes with value of type Log
.
That sounds a lot like simply a pair: (Log, Int)
. And that generalises to (w, a)
being a monad for each w
(with a
being the type parameter of the monad). The monoid constraint on w
that guarantees we can combine the log values also means that we have an obvious starting value (the identity element for the monoid: mempty
), so we don't need to provide anything to get a value out of a value in the writer monad.
The reasoning for the state monad to be s -> (a, s)
is actually pretty much a combination of the above; a State S Int
is an Int
that both depends on an S
value (as the reader depends on the environment) and also produces an S
value, where binding together a sequence of state computations should result in each one "seeing" the state produced by the previous one. A value that depends on a state value is a function of the state value; if the output comes "along with" a new state value then we need a pair.
Also, why are monads defined in terms of monadic functions that with type
a -> m b
, instead of with typem a -> m b
? What's so natural about a function going from a base type to a monad wrapped type?
(I took the libery of adding a space between m
and b
in mb
).
See, that's what makes it a monad. Without this, we already have functions a -> b
and a link from these to f a -> f b
(this link is called "functor", and obeys the laws for fmap
). But functor only gives you a projection of one "world" (a category) into the other - so that whatever laws hold in the first world, they also hold in the second world (for example, if a + b == c
, then f a (f +) f b == f c
). The monad gives you a bridge between the "worlds".
Also, you don't have to define the monad in terms of behaviour with functions of type a -> m b
, but one minimal specification of a monad tells you how >>=
, return
, id
and (.)
relate. It is possible to define the monad using >=>
, return
, id
and (.)
, or using join
, return
, id
and (.)
- you see, it doesn't really matter which function to pick. It turns out, >>=
is convenient for chaining.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With