Question

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.

Was it helpful?

Solution

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

  1. 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..

  2. 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.

OTHER TIPS

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 type m 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.

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) )?

Functions are just data. These just happen to be the types which fit the desired semantics best.

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?

You couldn't pass non-empty initial state, and couldn't modify it other than by appending values. If you do change this, you get the standard state monad (except for strings only).

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?

You'll note the Writer monad has a tell function which actually adds new data to the log.

Also, why are monads defined in terms of monadic functions that with type a -> m b, instead of with type m a -> m b? What's so natural about a function going from a base type to a monad wrapped type?

I think the best answer is "because a -> m b turns out to be more useful".

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top