Have you ever heard of monads? They have a reputation of being mysteriously difficult to understand. Is it really true? Let’s find out.
Haskell is a purely functional programming language and it’s really fun to learn. It will blow your mind, man, but learning it is out of scope of this little article. I will only show you a couple of things so that we can quickly jump to monads.
To write a result of sum of 2 and 5 we will have to write a tedious amount of code:
2 + 5
I know, sorry, but you have to. It will get easier though. But you can write the same thing another way:
(+) 2 5
And the result is the same. Why? Because “(+)” is a function of two parameters that returns a sum of its parameters, duh! So the syntax here is just to use spaces for functions and parameters like so:
min 9 10 //will return “9” [1,2,3,4] ++ [9,10,11,12] //will concatenate two lists and return “[1,2,3,4,9,10,11,12]”
Lambdas are anonymous functions and that’s all there is to them.
x -> x + 10
This is a lambda which adds 10 to a given parameter. (notice “” which is a syntax for lambdas)
Partially applied functions
As you know, in functional programming we can treat functions as parameters:
let mySum = (+) mySum 1 2 // will return “3”
But we can also partially apply the “(+)” function if we want:
let onePlus = (+) 1 onePlus 3 // will return “4”
Why? Well, because the “(+)” waits for two parameters before it can complete. When I said “let onePlus = (+) 1”, I gave the “(+)” function only one parameter and now it needs only one more parameter to return the value. Here are some examples that are a bit more interesting:
map (+1) [1..1000] // [1..1000] generates us a list of integers “1,2,3,4….1000” // Here I simply map a partially applied “(+1)” function to this list which will return a new list “2,3,4,5….1000” filter (<4) [1..10] // [1..10] generates us a list from 1 to 10. Later we use a partially applied “(>3)” function as an argument to the “filter” function. All this returns us a new list “[1,2,3]”
Cool, huh? Wanna see something even cooler?
“$” – this is a function applicator.
let minNine = min 9 // the partially applied function minNine 10 // returns 9 minNine $ 10 // retuns 9
Same result. You can use the “$” to apply the left function to the right parameter and that’s it. Sounds pretty pointless, but it allows you to do some cool things, like reducing the number of brackets you have to use, making your code cleaner. But we can also do something that will blow your mind:
map ($ 1) [(1 +), (2 *), (max 9)] // Here I create a list of partially applied functions (!!sick!!) and give them the parameter 1 // the result will be “[2, 2, 9]”
How cool is that? Pretty cool, I would say.
You can wrap your types in Haskell to gain more type control, for example if you have a String parameter, to be sure you won’t have it null, you can wrap it in a higher (superior?) type, the Maybe type.
Such a parameter can be of either “Just String” or “Nothing” type.
In Haskell, the monad for type Maybe is defined like so: instance Monad Maybe where
return x = Just x Nothing >>= f = Nothing Just x >>= f = f x fail _ = Nothing
Here we created an instance of Monad for Maybe and defined 3 functions inside:
return, “>>=”, fail
Then we defined their behavior:
“return” takes one parameter and wraps it inside a Type parameter Maybe (it takes x: returns “Just x”). This is the unit function that every monad needs. It takes a value and puts it into a minimal context that we need. For us, this context is the Maybe type.
return 10 //results in “(Just 10)” return “bla bla bla” // results in “(Just “bla bla bla”)” return [1..5] // will generate a list from 1 to 5 and then return it inside Just: (Just [1,2,3,4,5])
“>>=” is a function which behaves the following way:
“Nothing >>= f” results in “Nothing”. So if you try to use this function with “Nothing” and any other parameter, you will get “Nothing”
“Just x >>= f” results in “f x”. Here “f” is a function “f” that gets “x” as parameter. Basically, “>>=” is a function that takes a value of type Maybe and any function “f” and applies this function “f” to left parameter. This function “f” takes a parameter x, does some action and returns a result of same type but inside a Maybe.
(Just 3) >>= x -> return (1 + x) // results in “Just 4”. Here “x -> return (1 + x)” is a lambda which is treated as a parameter
What if you have:
a = Just 2 // so “a” is of type “Maybe Int”… you don’t have to declare its type in Haskell but the language will ensure type safety anyway
Now let’s define a lambda
let multiplyTenJust = x -> return (x * 10)
Now we can use our monad function “>>=” like so:
a >>= multiplyTenJust // results in “(Just 20)”
Because the left parameter and the result parameter are of exactly the same type, we can do something like this:
a >>= multiplyTenJust >>= multiplyTenJust >>= multiplyTenJust >>= multiplyTenJust //which results in 20000
So this is a monad – you define a monad as a type where you write special behavior for this type for return, “>>=”, fail functions (we didn’t describe “fail” here because we don’t need it at this point)
Monads are used when you want to extract a wrapped parameter from the wrapper type (like we extracted “2” from “(Just 2)”), and then apply a function to it that would return a wrapped value again. Monads are indeed difficult – they are difficult in their simplicity. To really use them you have to begin to understand them subconsciously and for that you need practice. Having a basic understanding of Monads you will quickly become the heart of all parties. Some people will love you, some people will envy you. With all this popularity you will have lots of additional responsibilities. But it’s worth it.