Update: Also see this post. It contains references to detailed explanation of monads and actual monad implementations and usage with source code.
As a late comer to functional programming (FP), I struggled a fair bit with understanding monads at first.
The ‘aha’ moment for me was that monads are really about function composition.
Function composition is somewhat of an alien concept for imperative programmers. Perhaps the concept of function composition does not sink deep enough for many newcomers to FP and thus they struggle when it comes to understanding monads later.
There is ample literature on the ‘net about monads so I will not go into any details here. However, considering that some may be in the same boat as I was, I will offer a view about monads that could prove to be helpful.
What is Function Composition?
In order to understand monads, we need to understand function composition first. Fortunately, it is not a hard concept to grasp. Function composition is really just taking two functions (with compatible input and output) and ‘glueing’ them together to create a new function.
As an example consider that we have a list of temperature values in Celsius. For display, we would like to convert them to Fahrenheit, round them off and format them to a string with an ‘F’ at the end. Assume we have three individual functions toFahrenheit, round and format (with compatible inputs and outputs); each does what its name implies. In F# we can compose them together to create a new function as follows:
- toFahrenheit >> round >> format
The symbol “>>” is the function bind operator. The round function is part of the F# system. The other two functions and some sample code to put it all together is shown below:
let toFahrenheit t = t * 9.0 / 5.0 + 32.0 let format f = sprintf "%0.0f F" f let tempsInCelsius = [32.0; 34.5; 17.9;] ;; tempsInCelsius |> List.map (toFahrenheit >> round >> format);;
Notice that we did not have to define any parameters for the new, composed function. The temperature parameter just comes from the toFahrenheit function.
Monads at a (very) High Level
Keeping function composition in mind, lets turn our attention to monads.
In order to understand monads we need to focus on two notions:
a) As part of our main computation, we want to do some ‘activity’ on the side however we don’t want this side activity to be too intrusive to our main code. The term activity is deliberately vague as it could be virtually anything (e.g. graceful error handling, logging, monitoring, asynchronous execution, etc.).
b) Even with the side activity happening we would like to continue using function composition in a relatively straightforward way.
The above two notions cannot work with simple function composition because there is no mechanism to handle the side activity. However if we wrap the data that our functions operate upon into monads we can achieve progress on this front.
So a monad is a kind of a wrapper that allows us to handle side activities in a graceful manner and lets us keep using function composition. The function composition with monads may look somewhat different but it is nonetheless still function composition.
For each type of side activity desired, a monad implementation is required.
Languages supporting higher kind of types – such as Scala and Haskell – even allow composition of multiple monads. Usually though, we are interested in only one type of side activity at a time. Exception is made for pure functional languages such as Haskell where side-effects can only happen inside a monad. This often requires composition of monads to handle multiple side activities at the same time.
I hope that for some of you out there this post clarifies an essential idea about monads that perhaps gets overlooked in the literature. Now when you look at monad tutorials, pay attention to the function composition aspect and maybe it will all begin to click.