Motivation
If you’re a programmer you are likely already using Monads quiet frequently. One popular example is Future in Java or Promise in JavaScript. You probably have seen or written code like fetch(url).then(do this).then(do that)
or you’ve used the async/await
syntax. You might have noticed that you cannot get a value out of the Promise. As soon as you write await
your function has to be async
(has to be a Promise itself). Or when you use .then()
you cannot get the value out of the then
. This is because the value only exist after your Promise (the computation) has be resolved, e.g. an API call has been made and the result has arrived.
In other languages like Haskell also things like Input/Output e.g. printing to the terminal or reading from a file happens to be inside a Monad, similar to the Promise in JavaScript. Because similar to JavaScript where you have to write the keyword async
to your function definition in order to use await
, in Haskell you have to specify the Monad type in the type annotation. For instance String -> IO String
is a function that takes a name for an environment variable e.g. $HOME
and gives you the contents of that variable.[1] This operation happens to be of type IO String
and not just String
because the contents the environment variable is dependent on your system and can change over time. But pure functions by definition always provide the same output for the same input. Therefore you get back an IO String
which basically means that as soon as the containing IO operation has been executed you will get back the contents of the environment variable. This is actually pure, because you always get back the same IO computation given the same input.[2] Now, if you have a function like String -> String
then you can be sure that there will be no side effects and no IO computation involved. And most of your functions can look like that, which allows you very easy reasoning of what kind of things can happen in your program, just by reading the type definition.
In this post I want to show you how easy it is to write your own Monad instance in Phyton. I have chosen to implement a list Monad, because it provides easy intuition and has much similarities with the Haskell implementation. We start by writing a Functor and Applicative instance for list in Python.
Functor
This is how Functor is defined in Haskell. Although it is named class you can think of it as an interface. This interface states that in order for something to be a Functor it has to implement the fmap
(or map
) function. A Functor is also often called “mappable”, because it is something we can map over like a list.
In Python we can use type hints to implement the Functor interface for list.
As you can see the implementation of the list Functor is very straight forward. The fmap
function simply takes a list fa
and applies the function given in the second argument of fmap
to every element of the list. As you can see we are now able to apply lambda functions to every list element and chain the results by repeated fmap
calls. There is also a builtin function in python for [f(x) for x in fa]
simply called map
. The same code, using the existing implementation, in Haskell would look like: (++"0") <$> (show) <$> (+ 1) <$> [1,2,3]
, where
<$>
stands for fmap
as operator.
Applicative
Next thing we are going to implement is Applicative. The interface for Applicative in Haskell looks as follows:
We have to implement two functions. The first one is very simple. Given a type a
lets say Integer, we simply have to put it into our structure, e.g. Int -> [Int]
. And the second function states, that given two Applicative, e.g. two lists, and one containing a list with a function, we simply apply the functions of that list to every element of the other list.
This is very similar to the implementation of Applicative for lists in Haskell fs <*> xs = [f x | f <- fs, x <- xs]
and [(++"0")] <*> ([(show)] <*> ([(+ 1)] <*> [1,2,3]))
yields the same result.
Monad
The Monad interface requires us to implement two functions. The return
is very similar to pure
, we just have to lift a value into the Monad, e.g. put a value into a list. The second function >>=
also called bind, takes a function that converts an a
to m b
e.g. from a -> [b]
in case of lists.
Now lets compare our Python list Monad implementation with the Haskell implementation.
As we can see the implementation is very similar, the bind function consists of concat
and map
, also called flatmap
. And this is how we can use the monadic bind function in Haskell [1,2,3] >>= \x -> [(x+1)] >>= \x -> [(show x)] >>= \x -> [x ++ "0"]
. Here you can find the full implementation of Functor, Applicative and Monad for list in Python:
https://gist.github.com/madnight/b0ae13f7908641655da688ebe7de22cb