Working through Haskell textbook chapters on different monads, I repeatedly get lost when the authors jump from explaining the details of bind and the monad laws to actually using monads. Suddenly, expressions like "running a function in a monadic context", or "to run a monad" pop up. Similarly, in library documentation and in discussions about monad transformer stacks, I read statements that some function "can be run in any choice of monad". What does this "running inside a monad" exactly mean?
There are two things I don't seem to get straight:
return
, >>=
) and laws. To "run" something inside a monad could thus either mean (a) to provide it as argument to return
, or (b) to sequence it using >>=
. If the monad is of type m a
, then in case a) that something must be of type a
, to match the type of the return
function. In case b) that something must be a function of type a -> m b
, to match the type of the >>=
function. From this, I do not understand how I can "run" some function inside an arbitrary monad, because the functions I sequence using >>=
must all have the same type signature, and the values I lift using return
must be of the specific monad type parameter.run
function such as runReader
, runState
, etc. These functions are not part of the definition of a monad, and they are plain functions, not in any way special imperative statements outside the functional core of the language. So, what do they "run"?I feel that having a clear understanding of these concepts is key to understanding monad transformer stacks or similar constructs that seem to be necessary to understand any substantial libraries and any non-trivial programs in Haskell. Thanks very much for helping me to make the leap from simply writing functional code to actually understanding what it means.
Authors who write books and articles often use metaphors and less precise language when they try to explain concepts. The purpose is to give the reader a conceptual intuition for what's going on.
I believe that the concept of 'running' a function falls into this category. Apart from IO
, you're right that the functions you use to compose, say, []
, Maybe
, and so on are no special from other functions.
The notion of running something inside of a monad comes, I think, from the observation that functors are containers. This observation applies to monads as well, since all monads are functors. [Bool]
is a container of Boolean values, Maybe Int
is a container of (zero or one) numbers. You can even think of the reader functor r -> a
as a container of a
values, because you can imagine that it's just a very big lookup table.
Being able to 'run a function inside a container' is useful because not all containers enable access to their contents. Again, IO
is the prime example, since it's an opaque container.
A frequently asked question is: How to return a pure value from a impure method. Likewise, many beginners ask: How do I get the value of a Maybe
? You could even ask: How do I get the value out of a list? Generalised, the question becomes: How to get the value out of the monad.
The answer is that you don't. You 'run the function inside the container', or, as I like to put it, you inject the behaviour into the monad. You never leave the container, but rather let your functions execute within the context of it. Particularly when it comes to IO
, this is the only way you can interact with that container, because it's otherwise opaque (I'm here pretending that unsafePerformIO
doesn't exist).
Keep in mind when it comes to the bind method (>>=
) that while the function 'running inside of it' has the type a -> m b
, you can also 'run' a 'normal' function a -> b
inside the monad with fmap
, because all Monad
instances are also Functor
instances.
the functions I sequence using >>= must all have the same type signature
This is only sorta true. In some monadic context, we may have the expression
x >>= f >>= g
where
x :: Maybe Int
f :: Int -> Maybe String
g :: String -> Maybe Char
All of these must involve the same monad (Maybe), but note that they do not all have the same type signature. Just as with ordinary function composition, you don't need all the return types to be the same, merely that the input of one function match up with the output of its predecessor.
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