My angle on Monads

Background

A few months ago I set out to learn exactly what monads where about. I had been reading into Functional Programming and could see benefits of adopting some of its techniques in my own code, albeit in an imperative language rather than declarative. Besides, JavaScript (JS) (my favourite language) is reported to be multi-paradigm; possessing Procedural, (prototypical) Object-Oriented and Functional Programming (FP) capabilities. And let’s face it, we could all learn to write better code.

My ambition was to learn enough about FP to improve my JS code and that included gaining an understanding of the infamous monad. My heart sank a little after reading the following quote from Douglas Crockford (YUIConf 2012):

In addition to it begin useful, it is also cursed and the curse of the monad is that once you get the epiphany, once you understand – “oh that’s what it is” – you lose the ability to explain it to anybody.

Source: http://www.i-programmer.info/news/167-javascript/5207-crockford-on-monads-and-gonads.html

But I was not deterred, after all I am an experienced developer of OOP, Procedural and even some Real-time in my day; and I am a graduate of Computer Science.

Many on-line articles, videos and tutorials later and I think I am just beginning to understand. So, more for my own benefit than anyone else’s, and before I become incapable of explaining it to others, I thought it best to capture my hypothesis here.

My findings

Besides their Category Theory foundation, monads represent a computational technique that any developer should be able to engage. The fact of the matter is that there are many interpretations because monads can be employed to solve a number of problems, so there is no single answer.

1) Monads are a design pattern for abstracting complex (upper) data types from simpler (lower) data types. Given a way of mapping data between data types (at least from lower to upper) a monad enables the processing of upper data using variations of lower data type functions. This is achieved through the development of some relatively simple functions: BIND, UNIT (aka Return) and LIFT. More details on these if you have the courage to keep reading.

2) Monads also facilitate the processing of data using a pipeline (expression-based) approach. Shove data in the front, turn the handle and watch the product pop out the other end. This is achieved through the “composability” feature of Monadic functions, courtesy of the Bind operator.

Monads-BindComp

If you have two upper functions they can be combined to make a super function with little/no apparent reference to the lower functions. To facilitate this the input and output interfaces of the monadic function (BIND) have to be the same type (monadic value aka upper data type). At the heart of this are the following:

  1. First-class functions – being able to pass functions as data in (as arguments) and out (as return values) of other functions. It must also be possible to manage functions as variables and store them in data structures (JavaScript has this nailed).
  2. High-order functions – functions capable of consuming one or more functions as arguments and/or return one or more functions as the product of the function.
  3. Lambda functions – more readily known in the JavaScript community as Anonymous functions (functions without a name).
  4. Currying – or partial definition of a function by only stipulating what data you have at the time and leaving the remaining arguments to the last minute.
  5. Closures – limiting the lexical scope of a function to variables declared within the same function as it was declared, and returning a reference to it.

Armed with these nuggets I have been able to follow along with many tutorials and reproduce the reported findings. However, it has only been within the last week (Dec 2015)a glimmer of light has been visible at the far end of the tunnel of my understanding. I have been led a merry dance by mathematicians who insist on presenting monads from the Category Theory perspective but my grasp was first formed using a very simple explanation.

Light at the end of the Monadic tunnel

Consider the use of the HTML bold tag <b></b>. If we have two data types; lower type [string] and upper type [HTML bold fragment] it should be easy to see how we convert from the lower to the upper form by prefixing and suffixing the [string] in bold tags.

Thus, the string “Example” converts (or maps) to “<b>Example</b>“. That’s stage one: understanding the nature of the upper and lower data types and how they map is essential.

Stage two: If we convert a string twice over the presentation is not double-bold so

<b><b>Example</b></b>

is equal to

<b>Example</b>.

Stage three: mapping two, already mapped strings

<b><b>heads</b><b> tails</b></b>

are equivalent to mapping the conjoined un-mapped strings once:

<b>heads tails</b>

The UNIT of the monad, which in this case is an empty string “”. This can be double mapped (as above) with another (non-empty) string in two ways, with the result being the same as not mapping the UNIT at all, and it does not matter which way round we do it:

<b><b></b><b>Example</b></b>

is the equivalent to

<b><b>Example</b><b></b></b>

because both result in 

<b>Example</b>.

Finally “Composability”; mapping

<b>heads <b>tails</b></b>

and

<b><b>heads</b> tails</b>

both result in

<b>heads tails</b>.

If we have two mapping functions. For instance if we introduced a lowercase to uppercase mapping function:

It does not matter which mapping we perform first (ignoring the letters in the html tag):

  1. lower to upper followed by making the string HTML:
    1. Example” becomes “EXAMPLE” and
    2. then “<b>EXAMPLE</b>“, or
  2. making the string HTML and the converting the content from lower to upper case:
    1. Example” ==> “<b>Example</b>
    2. ==> “<b>EXAMPLE</b>“,

with the same result.

Key Functions

The following functions provide facilities for converting data, and the functions that manipulate them, from simpler (Lower) form to complex (Upper/Monadic) forms.

monads-bindunit

UNIT (RETURN)

This function performs the conversion/mapping from Lower to Upper data types. It might be worthwhile formulating the reciprocal function, and/or extending this function so an optional second parameter can supply an Upper data variable to form the bases of a new variable.

Unit( Lower) -> Upper

BIND

The Bind function expects two arguments, a monadic (upper) value (mv) and a monadic (lifted) function (mf). The first argument (monadic value) is in the Upper form, which is checked and only used if valid. The second argument is a function that uses the lower form data (once extracted from its monadic value) to generate a new monadic value.

Bind( Upper, f(Lower) -> Upper) -> Upper

The Bind function enables composition of lower functions by wrapping them in upper forms. But the upper form can perform much more than calling lower functions, and wrapping/unwrapping data forms, such as: logging, state maintenance, mapping (filtering), etc.

BIND can be used in a wide variety of ways with the two most common being recursive or sequential. In the recursive form an MF will decide to call itself until a guard condition is met (such as running out of data or functions). The sequential approach employs a Do Monad function that is passed three things:

  • The Monad (Bind and Unit functions)
  • An array of Lower functions or data sets
  • A completion function, that extracts the Lower data from the processed Upper data.

LIFT

Given a function for processing Lower data from Lower data, it generates a function that expects Lower data, performs the required processing (using the original function), and returns the result in the Upper data type. This function calls on the UNIT function, described above, to convert the output of the original (lower to lower) function to the Upper data form. The result of LIFTing is a function that is based on the original (lower to lower) function but now produces data in the upper form. This is the Monadic (version of the original) function (mf).

Lift( f(Lower) -> Lower) -> f(Lower) -> Upper

Unit( f(Lower))(Lower) -> Upper

Monads-Lift

Critical Aspects to Monads

I think two of the critical aspects of using monads in this manner are:

  1. By using the Upper form functions the application is benefiting (through abstraction) from a more maintainable simpler code base.
  2. Upper form (BIND) functions perform the conversions to and from (U==>L then L==>U) Upper/Lower data types, in order to call Lower form functions but return data in the Upper form.

Parting Note

I still maintain my original opinion of monads for FP. They represent a kludge, a mechanism for isolating impure operations from the rest of a pure FP implementation. However, I can appreciate they have a very valid capability in a variety of use cases.

Update: December 2015

I am beginning to form the opinion that the reason Monads could be so difficult to grasp might be because they are even more an abstraction than a design pattern. Design Patterns are usually a implementation-agnostic but prescriptive approach to solving a specific, and very common, technical challenge. However, monads do not solve a specific problem but offer a more generic approach to addressing a range of problems in a declarative and purely functional manner. A meta-pattern if you will.

Update: January 2016

Ok, I am still not prepared to admitting I understand Monads but over this Christmas period I have had opportunity to consolidate my understanding and prepare my own tutorial. What I have learned from this confirmed my opinion above about the meta-pattern nature of Monads. At their heart Monads have the Bind (==>) operator that enables monadic-functions (standard functions, wrapped in a Unit function. Aka Lifted) to be chained together to form a compound process. The process can be curtailed early should a fault be detected (maybe monad), can be multi-tasked (list/sequence monad), or run through a defined series of steps depending on the output of the subsequent step (continuity monad).

Monads can give the appearance of over engineering of a basic process. This would be true if the Bind functions do nothing but act as callers for the base functions (identity monad) but the power of monads (from what I have gleaned) comes from being able to operate around the base functions, without disrupting the primary process, yet being able to adorn the process with additional capability such as logging and (at least the impression of) the preservation of state during processing.

Facilitating the Bind-based process is the ‘do monad’ operation that takes a list of functions, wraps then to form monadic functions and returns a function in preparation for execution. This is entirely consistent with the declarative basis of Functional Programming; define what needs to be achieved not how to achieve it. A focus on process over data.

The generated function can them be called with the required input data. The data passes through the base functions as prescribed but should a fault be detected by a Bind operation the process can be terminated gracefully. The Bind operation can orchestrate multiple execution of subsequent tasks within the process or perform addition capabilities such as reporting progress to the debugger (often impure activities.)

Update: March 2016

I have experimented with some JavaScript code to further discover the meaning of Monads and recorded the results in an article. I hope you find it helpful.

Update: July 2016

I think I finally understand what Monads are; and improved my understanding of Functors. Thanks to the YouTube Channel FunFunFunction by MPJ, check out his Functional Programming series – and Subscribe.

My new understanding is that Functors and Monads are a subset of data types. The sort of data type that is a collection of simpler data items.

Functors: posses an operator (generally) called Map (although it goes by a variety of names), which takes in a function and iterates over all the items in the collection. Map applies the function to each item and returns a new collection (exactly the same size as the original) containing the results (output) of the function.

Monads: go further. They are Functors in their own right but they also implement an operator called FlatMap (again aka other names), which is a combination of the Map and Flatten operations. This is particularly useful when data is nested and the application of Map alone would ultimately result in a multi-level collection. Although the application of Flatten would resolve the nesting, the frequency of this type of operation (utility of this data type) justify the efficient combination of Map and Flatten into a single operation.

In part 5 of my article entitled “Journey to Monad” I describe how the multiplication of arrays (matrices) can result in an array of arrays, and how a Flatten operation can be used to convert this into a single array.

I could still be wrong and deluded so will continue to read into the subject.

Bibliography

Advertisements