When I first learned about monads, the >>=
(aka ‘bind’) and return
functions made sense to me. The examples with the Maybe
instance also seemed pretty clear to me— Nothing
s stay as such.
instance Monad Maybe where
(Just x) >>= f = f x
Nothing >>= _ = Nothing
return x = Just x
What always confused me were data types like Parser
, Reader
, and State
:
newtype Parser a = Parser { runParser :: String -> ([a], String) }
newtype Reader r a = Reader { runReader :: r -> a }
newtype State s a = State { runState :: s -> (s, a) }
Part of my confusion is my dislike for Haskell’s implicit arg in record accessors; runState
is actually runState :: State s a -> s -> (s, a)
.
A second part of my confusion is the imprecise naming. The State monad holds more than just state, it is by definition a function from initial state s
to a tuple of a new state s
and a result value a
.
A lot of verbage regarding monads concerns ‘side effects’ and ‘computation(s)‘. Again, side effects seem pretty straightforward; examples are printing to stdout, writing to a file, or executing a database transaction. (Note that these examples are in the IO monad). Computation, on the other hand, felt very abstract to me. I could not tell the difference between the expressions 1 + 2
, foldl (+) 0
, and do { val <- State.get; return (length val)}
.
It wasn’t until I made the connection that do-notation
desugars to a large expression of bind (and >>
) operators, and that the result type is still a monad. Close to a program’s entry-point the programmer usually wants some basic value like an Int
or String
. Being a monad, there is no default way to go from m a -> a
. However, what usually is implemented are the run
functions, which return a ‘normal’ type a
.
It is this notion of extraction, especially through a lazy-evaluation lens, where the term ‘computation’ makes sense to me. A monad-returning function only adds transformation steps. When run
, these transformations are completely applied to get back a usable value.
Coming back full-circle, for the Maybe
example runMaybe
can be thought of as pattern matching on Just
. This runs into the issue that runMaybe
would be partial and lead to some panic/error propagation, but this is usually desired given the semantics of Nothing
.