Programmable semicolon, monads in Haskell

Monads are programmable semicolons, thats it. For a programmer a monad provides functions which allow for sequencing actions. Moreover between every two following actions, a specific “code snippet” gets executed. To make everything more clear, let’s begin with a little background information.

In imperative languages like C or Java, semicolons are used to express a sequence of operations. The code in front of the semicolon gets executed before the code that comes after the semicolon. In languages like Haskell, where more or less everything is just an expression, it’s not apparent at first glance how to sequence two parts of a program.

Nice to know: Monads originate from a mathematical field called “Category Theory”. For using and understanding monads in Haskell it’s not necessary to know the definitions and theorems of this mathematical discipline.

So, if sequencing is easier to do in other languages, is Haskell badly designed? No, not at all. One could say, that it was intentional to make sequencing more difficult. Haskell was designed to be referential transparent, which is just fancy wording for saying that each function call, can be replaced with its return value. Imagine you have a function named “doCalculation” which takes a number, does some calculation and returns a number. In Haskell the expression “doCalculation(x) + doCalculation(x)” can be replaced with “2 * doCalculation(x)” and its guaranteed, that the behavior of the program won’t change. In Java this replacement can’t be done in general, as it’s not assured that “doCalculation” returns the same result on every call. Also, in Java “doCalculation could have side-effects. In our example it can be seen, that referential transparency comes with a lot of advantages. Optimizations are easier for compilers of referential transparent languages. Moreover, parallelism can be automated to some degree. However all this features come with the cost of more difficult sequencing. But for that matter we can make use of monads.

Example

So how can such a “semicolon” be programmed and used? Let’s answer this question with an example: Creating and editing shopping lists. A shopping list gets represented as a list of strings. We will implement functions which allow for adding items, one after another, to a shopping list, as well as removing the first item on a list. Additionally we want to count the number of times a shopping list has been manipulated (adding and removing items).

To begin with, we have to define a new type.

“Counter” consists of an integer named “counter” and an arbitrary element. For our shopping lists, the element will be a list of strings. The counter will represent the number of manipulations done to that list.

Let’s get to the important part. To program our “semicolon” and thus make sequencing possible, we do the following.

The above code is where all the magic happens. To begin with the more straight forward function, “return” just constructs a new “Counter” for a given element. It can be used to construct a new “Counter” starting with 0 manipulations for a given shopping list.

“>>=” is the “programmable semicolon”. As everything in Haskell it’s just another function. In our example it is of type “Counter a -> (a -> Counter b) -> Counter b” (for the shopping lists “a” and “b” will both be lists of strings). Let’s go slowly. “>>=” gets a “Counter” with an element (called “x” in the code), it manipulates the element with a given function, which again returns a “Counter”. Finally “>>=” returns a new “Counter”, in which the integer representing the number of manipulations, is the integer of the given “Counter” plus the integer of the “Counter” returned by the given function plus 1.

What’s crucial in the definition of our programmable semicolon, is that the “Counter” – given in the first argument – has to be known, before the function – given in the second argument – can be evaluated. So if the “Counter” in the first argument (which comes before “>>=”) is just an expression resulting in a concrete “Counter”, it is ensured that this expression will be evaluated before the function given in the second argument (which comes after “>>=”) will be called.

To be able to add items to our shopping lists as well as removing items, we just need to define two more functions, which both are one-liners. Moreover we define our first shopping list, namely the empty shopping list.

At this point, we have everything at hand to create a typical shopping list in a sequential fashion.

Executing the code, leads to the following output.

So, what happens here? We begin with the empty shopping list, which has no elements in it and its counter is initialized to 0. Our programmable semicolon “>>=” takes the list resulting from a line and passes it to the function in the next line. Essentially the empty shopping list gets handed through the lines and manipulated at each. The central point is, that we are guaranteed that for example “Bread” is added before “Butter”. That is, as described above, a result of the definition of our programmable semicolon. But notice how everything in our, as called, monadic action is made up of functions. Each line is an anonymous function (except the first), as well as “>>=”. So in conclusion, we have implemented a way to sequence actions in a purely functional way.

Haskell provides a lot of syntactic sugar, which allows for writing the monadic action above in a way, which more emphasizes the sequential property.

The two definitions are completely equivalent. In the end it’s a matter of taste which one to use. At the beginning however, it’s advisable to use the explicit form, to be reminded what’s going on behind the scenes.

One important fact about the counting of manipulations still needs to be pointed out. Once defined in the programmable semicolon, the counting is completely invisible within the monadic action. The magic happens inside our semicolon “>>=”. As the functionality of this function can be freely defined, it’s a great place to hide all sorts of things. The predefined maybe monad, for example, hides checking for null in its programmable semicolon and skips all following lines if null occurs. Moreover logging can be implemented in monadic way and hidden in “>>=”. Parsers are another application, which make use of the state monad, but that’s a whole other story.

So monads are very useful in a variety of scenarios and in the end nothing more and nothing less than a just programmable semicolons.

Leave a Reply

Your email address will not be published. Required fields are marked *