Sunday, May 3, 2009

Monads in C#

Ready to learn monads?

Here is a tutorial for the rest of us. Let us begin.

I'm dreaming of some code, a function that takes a person's name and returns true if he can be found in the phone book, Facebook, and MySpace; it returns false otherwise. Our first write of the code might look like this:

   18             if (IsInPhoneBook("John") && IsInFacebook("John") && IsInMySpace("John"))

   19             {

   20                 return true;

   21             }

   22             else

   23             {

   24                 return false;

   25             }



We can wrap it in a method of this signature somewhere:

27 public Boolean PersonCanBeFound(String name)



However, with this implementation we are effectively locked into the expression that checks for John in all three medias. Suppose if we wish to check against only Facebook and MySpace, then we are outta luck. So, let's explore another behavior:

   29 var result = MaybeJohn.IsInPhoneBook().IsInFacebook().IsInMySpace();



Fluent interface allows us to chain the calls. MaybeJohn is of type Maybe such that:

   28 Maybe<String> MaybeJohn = new Maybe<String>() { Value = "John"};

   29 var result = MaybeJohn.IsInPhoneBook().IsInFacebook().IsInMySpace();



where we can use it like so:

   30 if (result.IsNothing)

   31 {

   32     //prints "John cannot be found!"

   33     Console.WriteLine("{0} cannot be found!", MaybeJohn.Value);

   34 }

   35 else

   36 {

   37     //prints "John found!"

   38     Console.WriteLine("{0} found!", MaybeJohn.Value);

   39 }



The IsNothing attribute stores our result: It stores _True_ if John can be found in the phonebook and Facebook and MySpace. Else it holds _False_. Simple enough. Let's code up the Maybe class. Here's our first stab:

   74 public class Maybe<T>

   75     {

   76         public T Value;

   77         public Boolean IsNothing = false;

   78 

   79         public Maybe<T> IsInFaceBook()

   80         {

   81             //Only do work if we don't have an answer yet

   82             if (!this.IsNothing)

   83             {

   84                 //let's do some work here through some imaginary service

   85                 Boolean isFound = new SomeService().Lookup("phonebook", Value);

   86 

   87                 //set the property

   88                 this.IsNothing = !isFound;

   89                 return this;

   90             }

   91             return this;

   92         }

   93 

   94         public Maybe<T> IsInPhoneBook()

   95         {

   96             //Only do work if we don't have an answer yet 

   97             if (!this.IsNothing)

   98             {

   99                 //let's do some work here through some imaginary service

  100                 Boolean isFound = new SomeService().Lookup("facebook", Value);

  101 

  102                 //set the property

  103                 this.IsNothing = !isFound;

  104                 return this;

  105             }

  106             return this;

  107         }

  108 

  109         public Maybe<T> IsInMySpace()

  110         {

  111             //Only do work if we don't have an answer yet

  112             if (!this.IsNothing)

  113             {

  114                 //let's do some work here through some imaginary service

  115                 Boolean isFound = new SomeService().Lookup("myspace", Value);

  116 

  117                 //set the property

  118                 this.IsNothing = !isFound;

  119                 return this;

  120             }

  121             return this;

  122         }

  123     }



The code works, but it smells of:
1) duplication. All the methods are identical except for the SomeService.Lookup call.
2) tight coupling. This is more subtle, but it's not the best idea to imbed the service calls (i.e. database/webservice call) into this class. It tightly couples the logic to use a service call when service calling is just a periphery issue. I'd rather be able pass in an implementation that may or may not use SomeService.Lookup.

Let's extract the duplicate code inside the _if_ block into an independent function outside of the Maybe class. The function should take in a couple args since we no longer have the luxury of the _this_ pointer. Here it is written as a first-class function:

   41 Func<Maybe<String>, String, Maybe<String>> CheckMedia =

   42     (maybe, media) =>

   43         {

   44             //some imaginary service

   45             Boolean isFound = new SomeService().Lookup(media, maybe.Value);

   46 

   47             maybe.IsNothing = !isFound;

   48             return maybe;     

   49         };



To use the CheckMedia function, we pass it as an arg to the methods in the Maybe class:

   52 var result = MaybeJohn.IsInPhoneBook(CheckMedia)

   53                       .IsInFacebook(CheckMedia)

   54                       .IsInMySpace(CheckMedia);



and we make some changes to the Maybe class methods to accept the first-class function and to execute it:

   37     public class Maybe<T>

   38     {

   39         public T Value;

   40         public Boolean IsNothing = false;

   41 

   42         public Maybe<T> IsInFaceBook(Func<Maybe<T>, String, Maybe<T>> mediaCheck)

   43         {

   44             if (!this.IsNothing)

   45             {

   46                 return mediaCheck(this, "phonebook");

   47             }

   48             return this;

   49         }

   50 

   51         public Maybe<T> IsInPhoneBook(Func<Maybe<T>, String, Maybe<T>> mediaCheck)

   52         {

   53             if (!this.IsNothing)

   54             {

   55                 return mediaCheck(this, "facebook");

   56             }

   57             return this;

   58         }

   59 

   60         public Maybe<T> IsInMySpace(Func<Maybe<T>, String, Maybe<T>> mediaCheck)

   61         {

   62             if (!this.IsNothing)

   63             {

   64                 return mediaCheck(this, "myspace");

   65             }

   66             return this;

   67         }

   68     }



It works, and a lot less code!

We can do better still.

If we were to extend functionality and add a new media to check, say Twitter, we'd have to crack open the Maybe class and add another method named IsInTwitter.

Maintenance headache.
Let's only have one method in the Maybe class such that we can achieve something like:

   56 var result =

   57     MaybeJohn.IfFound(/*...pass in some function to do CheckMedia for John in facebook...*/)

   58              .IfFound(/*...pass in some function to do CheckMedia for John in myspace...*/)

   59              .IfFound(/*...pass in some function to do CheckMedia for John in twitter...*/);



The IfFound method takes in a function that transitions MaybeJohn to the next state as input for the following IfFound call. The function takes a Maybe, does work (CheckMedia) on it, and spits out the Maybe. Here we have it:

   60 var result = MaybeJohn.IfFound(john => CheckMedia(john, "facebook"))

   61                       .IfFound(john => CheckMedia(john, "myspace"))

   62                       .IfFound(john => CheckMedia(john, "twitter"));



The function takes advantage of lambda syntax. Next we make the changes for the Maybe class. The IfFound method takes in a first-class function and executes it:

   22     public class Maybe<T>

   23     {

   24         public T Value;

   25         public Boolean IsNothing = false;

   26 

   27         public Maybe<T> IfFound(Func<Maybe<T>, Maybe<T>> f)

   28         {

   29             if (!this.IsNothing)

   30             {

   31                 return f(this);

   32             }

   33             return this;

   34         }

   35     }



Boom that's it.

Wait. Where's the monad?

The concepts are all here. In functionese, the IsFound method is the Bind operator and in functionworld it is defined independently of any class. There is another function called Unit which we don't have here. Unit is simply a function that wraps the Maybe object around the string "John" to create MaybeJohn; We don't have this, we just new'ed one up. The term _monad_ refers to the Maybe class which is the container/wrapper. We have created the Maybe monad, and as they say "it's the simplest one of all."

In the next post I will show a practical reuse of this Maybe monad we've created.

No comments:

Post a Comment