Much Ado About Monads – Maybe Edition Wednesday, January 28, 2009

In the past I’ve extensively talked about creating monads, but not as much more around them in terms of why you might use them and what problems they are trying to solve. Since that time, I’ve been challenged by others to actually prove not only that it can be understood by everyone, but they are very useful as well. It’s been a form of geekery among us software professionals to say, “Whoopee! I’ve learned how to implement a monad to do x” without really explaining the reason why people should care.

This series of posts is set to dispel some of those qualms people have with the word monad and why it might be useful in other languages besides Haskell, from which the term in the computer science world came. In the past here, I’ve talked extensively about them, so I won’t necessarily bore you with the excruciating details, but I still have to answer the question in a brief and to the point answer, what exactly is a monad anyways?

What is a Monad?

For those not really interested in category theory and such, let’s get to a basic definition instead. Think of it as a basic abstract data type used to represent computations. We can abstract away complex behavior, such as IO actions, asynchronous behavior uniformly in such a way that we can sequence these operations together. Here’s the best definition of all, “We can abstract the complicated things out of the way so that we can write a program that we want to read it instead of how it really works”. I’d rather see the focus not on the category theory part, as that is interesting and a key to what’s going on here, but instead as a nice design pattern for abstracting complexity.

Now the question arises, how and when do we use them? When does it make sense? To understand that, we need to look at the problems we’re facing. Let’s take an example of chaining conditionals together which may or may not succeed.

What’s the Problem?

To really understand why it’s useful, we first have to understand the problem we’re trying to solve. As I stated above, let’s first take a look at chaining together conditional operations which may or may not succeed. Let’s take for example a series of lookups against some hash tables in a way where one lookup depends on another. Typically in C#, our code might look like the following. It’s naive code in terms of the style but I’ve seen this pattern time and time again in code:

class Program

{

private static readonly Dictionary< string , string > employeeDept =

new Dictionary< string , string >

{

{ "John" , "Sales" },

{ "Bob" , "IT" }

};

private static readonly Dictionary< string , string > deptCountry =

new Dictionary< string , string >

{

{ "IT" , "USA" },

{ "Sales" , "France" }

};

private static readonly Dictionary< string , string > countryCurrency =

new Dictionary< string , string >

{

{ "USA" , "Dollar" },

{ "France" , "Euro" }

};



public static string GetEmployeeCurrency( string name)

{

string dept;

string country;

string currency;



if (!employeeDept.TryGetValue(name, out dept))

return null ;



if (!deptCountry.TryGetValue(dept, out country))

return null ;



return !countryCurrency.TryGetValue(country, out currency)

? null : currency;

}



static void Main( string [] args)

{

var johnCurrency = string .Format( "John's currency: {0}" ,

GetEmployeeCurrency( "John" ));

var peteCurrency = string .Format( "Pete's currency: {0}" ,

GetEmployeeCurrency( "Pete" ));



Console.WriteLine(johnCurrency);

Console.WriteLine(peteCurrency);

}

}

As you’ll notice from the above code, we have three dictionaries of various information that we’re using as lookups. And you’ll also note that we have to go through a bit of hassle just to determine if one’s in a list, and if so, continue onto the next. Similarly, in F#, we run into that same issue as well, with an example of how that could be a problem.

let employeeDept = Map . of_list ( [ ( "John" , "Sales" ) ; ( "Bob" , "IT" ) ] )

let deptCountry = Map . of_list ( [ ( "IT" , "USA" ) ; ( "Sales" , "France" ) ] )

let countryCurrency = Map . of_list ( [ ( "USA" , "Dollar" ) ; ( "France" , "Euro" ) ] )



let employeeCurrency name =

match Map . tryfind name employeeDept with

| None -> None

| Some dept ->

match Map . tryfind dept deptCountry with

| None -> None

| Some country ->

Map . tryfind country countryCurrency



let johnCurrency = sprintf "John's currency: %A" ( employeeCurrency "John" )

let peteCurrency = sprintf "Pete's currency: %A" ( employeeCurrency "Pete" )

And indeed, you could run into this pattern as well in Haskell without trying really hard either:

import qualified Data.Map as M



employeeDept :: M.Map String String

employeeDept = M.fromList([( "John" , "Sales" ), ( "Bob" , "IT" )])



deptCountry :: M.Map String String

deptCountry = M.fromList([( "IT" , "USA" ), ( "Sales" , "France" )])



countryCurrency :: M.Map String String

countryCurrency = M.fromList([( "USA" , "Dollar" ), ( "France" , "Euro" )])



employeeCurrency :: String -> Maybe String

employeeCurrency name =

case M .lookup name employeeDept of

Nothing -> Nothing

Just dept ->

case M .lookup dept deptCountry of

Nothing -> Nothing

Just country ->

M .lookup country countryCurrency

Just watch that code walk off the screen to the right! Are you starting to notice a pattern here? I sure am! Now the question arises, what can we do about it? How can we write the code the way it should look instead of all the conditional logic we’ve had to bake in. Wouldn’t you rather write it something like this?

// Pseudocode F#

let employeeCurrency name =

let dept = Map . tryfind name employeeDept

let country = Map . tryfind dept deptCountry

Map . tryfind country countryCurrency

Through the power of monads, we can, at least something very similar to this. So, let’s find out how.

Introducing Monads

Monads are a concept in the computer science field to come from the Haskell programming language. Most people assume, oh, it’s only used for IO, but that’s simply not true at all. Instead, we can treat it as a common abstraction to solve any number of problems, including the conditional logic from above which may succeed or fail.

To create this abstraction, Haskell has a Monad type class which allows us to generalize the application of them. Let’s look at exactly what that looks like:

class Monad m where

-- Sequentially compose two actions

-- passing any value produced from the first to the next

(>>=) :: m a -> (a -> m b) -> m b



-- Sequentially compose two actions

-- discarding any value produced from the first

(>>) :: m a -> m b -> m b



-- Inject a value into the monadic type

return :: a -> m a



-- Fail with a message

fail :: String -> m a

I added comments to each function that the type class implements to give you an idea about what each one of them does. What’s our ultimate goal here? Well, it’s the ability to string together functions that may or may not succeed and to sequence these instructions together. With an implementation of a monad, we get that ability.

Now, let’s move onto the Maybe implementation. What is maybe anyways? It’s a simple algebraic data type which allows for just a value or no value at all such as this:

data Maybe a = Nothing | Just a

deriving ( Eq , Ord )

Now that we understand that, let’s move onto looking at the monad instance for the Maybe type.

instance Monad Maybe where

( Just x) >>= k = k x

Nothing >>= _ = Nothing



( Just _) >> k = k

Nothing >> _ = Nothing



return = Just

fail _ = Nothing

These are defined in the base class libraries in Haskell, so there is no need to implement this yourself. What this allows us to do is simply our code dramatically. Let’s look at the sugared and de-sugared examples:

-- Imperative style

employeeCurrency' :: String -> Maybe String

employeeCurrency' name = do

dept <- M.lookup name employeeDept

country <- M.lookup dept deptCountry

M.lookup country countryCurrency



-- De-sugared version on one line

employeeCurrency'' :: String -> Maybe String

employeeCurrency'' name =

lookup employeeDept name >>= lookup deptCountry >>= lookup countryCurrency

where lookup = flip M.lookup

Wow, looks much nicer! How, you say? Because we get to see the code as we’d like to read it, as a script instead of a bunch of nested if statements. But, how does this apply to F# and other .NET languages? The answer is actually simple and straight forward.

Maybe F#?

Now that we understand some of the basics and how it can be done in Haskell. Let’s translate these ideas to F#. First, we need to consider the Option type, which is a similar implementation to the Haskell Maybe type. Knowing what we’ve learned in previous posts on my blog about how monads can be implemented, let’s look at a standard F# implementation.

[< AutoOpen >]

module MaybeMonad =

let bindM x k =

match x with

| Some x -> k x

| None -> None



let returnM x = Some x



type MaybeBuilder ( ) =

member x . Bind ( x, k ) = bindM x k

member x . Return ( x ) = returnM

member x . Delay ( f ) = f ( )

When we define a builder such as we’ve done above, we now have the ability to use the syntactic sugar to wrap our code up in blocks rather nicely. This is a bare implementation of the monad, as I could add additional features such as support for using bind statements and such. The Expert F# book walks through this in detail in the Language Oriented Programming section. We could use either the sugared or de-sugared versions if we do a little bit of work on our part to make this happen. Let’s see how we implement this behavior to get what we want:

// Sugared syntax

let maybe = new MaybeBuilder ( )

let employeeCurrency' name =

maybe { let ! dept = Map . tryfind name employeeDept

let ! country = Map . tryfind dept deptCountry

return ! Map . tryfind country countryCurrency

}



let johnCurrency' =

sprintf "John's currency: %A" ( employeeCurrency' "John" )

let peteCurrency' =

sprintf "Pete's currency: %A" ( employeeCurrency' "Pete" )



// Desugared syntax

let flip f x y = f y x

let ( >>= ) = bindM

let employeeCurrency'' name =

let lookup = flip Map . tryfind

lookup employeeDept name >>= lookup deptCountry >>= lookup countryCurrency



let johnCurrency'' = sprintf "John's currency: %A" ( employeeCurrency'' "John" )

let peteCurrency'' = sprintf "Pete's currency: %A" ( employeeCurrency'' "Pete" )

This gives us the flexibility we need as programmers to use whichever abstraction we see fit. Unfortunately, due to type classes not really being something you can easily do in F#, we had to take some liberties with the bind operator and so on. As an imperative programmer, the sugared syntax makes absolute sense about what I’m trying to accomplish. It reads much like a script and to not have to worry about the noise associated with nested if statements littering the codebase.

And yes, you could use the lessons from my Functional C# libraries on MSDN Code Gallery to also implement this in C#, as I’ve covered in the past. With the careful use of extension methods and a little know how, you too can write LINQ statements such as this to determine whether an employee’s currency can be found. As I’ve said earlier, you can use the LINQ syntax to do things that don’t deal with collections at all, such as this:

public static Maybe< string > GetEmployeeCurrency( string name)

{

return from dept in employeeDept.TryFind(name)

from country in deptCountry.TryFind(dept)

from currency in countryCurrency.TryFind(country)

select currency;

}

The more you keep working with these abstractions, the more you’ll understand how to use them and where they apply. For further reading, I always recommend the Real World Haskell book which goes over these design patterns in detail.

Conclusion

Now, I think through this careful exploration, you may have a better understanding of these design patterns or at least how you might be able to abstract some sort of uniform complexity. There’s more to these patterns than just the maybe, as there’s the ever so familiar list and error, but also some interesting ones that are unique to F# in the async “workflow”. What I’m really trying to drive home here is that ideas from functional programming aren’t just for guys in lab coats or those at the universities, but for general line of business developers as well. Stepping out of that comfort zone that is C# or VB and into a more functional language will give you a greater appreciation of different styles and improve your day to day coding.