Monads explained yet again

Update: This explanation is incorrect. Monads make Kliesli arrows composable. Every other explanation only grabs at one application of a Monad.
Asking what are monads is the wrong question, the more important question is why did we end up inventing it.


Suppose a function has side effects. If we take all the effects it produce as the input and output parameters, then the function is pure to the outside world.

So for an impure function:


f’ :: Int -> Int


we add the RealWorld to the consideration


f :: Int -> RealWorld -> (Int, RealWorld)
— input some states of the whole world,
— modify the whole world because of the a side effects,
— then return the new world.


then f is pure again. We define a parametrized data type IO a = RealWorld -> (a, RealWorld), so we don’t need to type RealWorld so many times


f :: Int -> IO Int


To the programmer, handling a RealWorld directly is too dangerous—in particular, if a programmer gets their hands on a value of type RealWorld, they might try to copy it, which is basically impossible. (Think of trying to copy the entire filesystem, for example. Where would you put it?) Therefore, our definition of IO encapsulates the states of the whole world as well.


These impure functions are useless if we can’t chain them together. Consider


getLine :: IO String               = RealWorld -> (String, RealWorld)
getContents :: String -> IO String = String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO ()        = String -> RealWorld -> ((), RealWorld)


We want to get a filename from the console, read that file, then print the content out. How would we do it if we can access the real world states?


printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
                       (contents, world2) = (getContents filename) world1 
                   in  (putStrLn contents) world2 — results in ((), world3)


We see a pattern here: the functions are called like this:


(, worldY) = f worldX
(, worldZ) = g worldY


So we could define an operator ~~~ to bind them:


(~~~) :: (IO b) -> (b -> IO c) -> IO c


(~~~) ::      (RealWorld -> (b, RealWorld))
      -> (b -> RealWorld -> (c, RealWorld))
      ->       RealWorld -> (c, RealWorld)
(f ~~~ g) worldX = let (resF, worldY) = f worldX in
                        g resF worldY


then we could simply write


printFile = getLine ~~~ getContents ~~~ putStrLn


without touching the real world.


Now suppose we want to make the file content uppercase as well. Uppercasing is a pure function


upperCase :: String -> String


But to make it into the real world, it has to return an IO String. It is easy to lift such a function:


impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)


this can be generalized:


impurify :: a -> IO a


impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)


so that impureUpperCase = impurify . upperCase, and we can write


printUpperCaseFile = 
    getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn


Now let’s see what we’ve done:
  1. We defined an operator (~~~) :: IO b -> (b -> IO c) -> IO c which chains two impure functions together.
  2. We defined a function impurify :: a -> IO a which converts a pure value to impure.
Now we make the identification (>>=) = (~~~) and return = impurity, and see? We’ve got a monad.


I think, this is the simplest explanation of what are monads and more importantly why they are so.







Formal Sidenote (can be safely ignored): (>>=) and return must satisfy the following axioms for the type they are inside to be a monad:
  1. return a >>= f = f a
  2. f >>= return = f
  3. f >>= (\x -> g x >>= h) = (f >>= g) >>= h

5 thoughts on “Monads explained yet again

    • I feel there are two sides to the usefulness of a monad. The human side and the compiler side. On the human side, it is a way to write less code, and thus make it less buggy i.e. it is just syntactic sugar.

      From the compiler’s side, the pure & impure parts of a function a separated into different types. So it is easy to check correctness and optimize easily.

      Microsoft is using the Monad pattern to implement LINQ in C# and VB. That is why LINQ can generate SQL, search through XML and arbitrary data structures by using the same code base. It is also easy to parallelize LINQ, just add .asParallel() call, the entire programs suddenly become parallel when necessary.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s