7
votes

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:

  1. A monad is a type class with functions (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.
  2. In my understanding, there is no notion of execution or running a computation in a functional language - there is only function application to some argument, and evaluating the function (replacing it with its value). Yet, many specific monads come with a 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.

3

3 Answers

7
votes

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.

1
votes

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.

0
votes

Here is a simple analogy of "run the function inside the container", with pseudocode:

Let's say you have some type Future[String] which represents a container that will have a string "at some time in the future":

val tweet: Future[String] = getTweet()

Now you want to access the string -- but you do not take the string out of the context -- the "future" -- you simply use the string "inside the container":

tweet.map { str =>
  println(str)
}

Inside these curly braces, you are "in the future". For example:

val tweet: Future[String] = getTweet()

tweet.map { str =>
  println(str)
}

println("Length of tweet string is " + tweet.length) // <== WRONG -- you are not yet in the future

The tweet.length is trying to access the tweet outside of the container. So "being inside the container" is analogous to, when reading the source code, "being inside the curly braces of a map (flatmap, etc.)". You are dipping inside of the container.

tweet.map { str =>
  println("Length of tweet string is " + str.length) // <== RIGHT
}

Albeit a very simple analogy, I find this useful when thinking of all monads in general. In the source code, where is one "inside the container" and where is one outside? In this case, the length function is running in the future, or "inside the container".