11 June, 2012

Easy Monad

A monad is a class that has two things:

  • A field that contains an object
  • A higher-order function


The higher-order function is the key. This function takes in argument a normal function. It then apply this normal function to the object the monad contains. The normal function have to have an output type other than void. Whatever the output of normal function is, its then converted into the monad type and returned.

The higher-order function can be made to take a normal action instead of a normal function. The difference between a normal function and a normal action is simple: normal function have an output, action don't. In this case, since there is no output, therefore the higher-order function do not return anything.

The action thing is something that go against the functional programming philosophy. In functional programming, a function have to return something. If nothing else then the object itself. This makes the basis for method-chaining. The action thing is there to make monads compatible with imperative programming where functions often don't return anything.

Here is the simplest, yet useful monad:


public class M
{
    public readonly T _o;

    public Monad(T o)
    {
        _o = o;
    }

    public M Bind(Func f)
    {
        return f(o).ToM();
    }
}

public class ExtensionMethods
{
    publc static M ToM(this T o)
    {
        return new M(o);
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        string s = "some string";
        Console.WriteLine(s.ToM.Bind(s => s.Substring(0, 1)).Bind(s => s.charAt(0)).Value);
    
    }
}


Explanation

M is the monad. It has a field called o which can contain an object of any type. This object has to be passed through the constructor, it cannot be written directly into even though it is public, because its readonly.

Its the Bind method that makes M different than an ordinary class. Bind is a higher-order function. Its argument is a Func, i.e. a function. An entire function can be passed in argument to Bind. This function is applied to the field object _o. The return value of the function is casted into M and returned.

Why do we have to cast the return value, and why into M? Because we want to have method-chaining. In the second line of Main method, we first call the Bind method and then what is return is binded to another function. More explanation below.

In the Main method we have an object s. It can be of any type because any type can be casted into M. This is achieved by the extension method. s is converted into M by calling the ToM method. Now what we have is a monad. This is called lifting, we have converted a normal type (string in this case) in a "higher" type (M in this case). The normal type do not support higher-order functions, the monad do.

Once we have a monad, we call the higher-order function on it. The argument of Bind is body of a function. This need not be an anonymous function. We can very well do this:

Func func = s => s.Substring(0, 1);
string s = "some string";
s.ToM.Bind(func);


Whatever function we pass to Bind is applied on the object contained inside the monad. A monad therefore is a wrapper, it wraps an object of normal type. A monad however is more than a wrapper, it also has a higher-order function and related features as shown above.

In the Main method we are having full use of method chaining. We are calling one method, this results in some changes in the object on which this method is called, the return value is itself a monad and so we can call another method on it and so on. This is the power of monad.

Uses of Monad



A monad can be used for logging. Instead of calling methods on an object directly, we call the Bind method and pass on the method to it, the Bind then calls the method. We can insert our desired logging related code before and/or after calling of the normal function inside the body of Bind. Like this:

public M Bind(Func f)
{
    /* logging code, before calling the normal method */
    f(_o).ToM();
    /* logging code, after calling the normal method */
}
The second use is putting the exception handling logic, that is common to all methods. Like this:

public M Bind(Func f)
{
    try
    {
        f(_o).ToM();
    }
    catch(/* common exception class */ x)
    {
        /* common exception handling code */
    }
}
The thing is, you can send any logic in argument to Bind. For example you have a class with 4 variables and you want to change values of some variables on basis of a complex predicate applied to some other variables. You can even have a for loop's logic put in. Whatever the logic you want to send, the monad class itself need not be changed.

A monad is not designed to have any logic. A monad is designed to have meta-logic. Logic behind logics. The meta-logic can be a place holder of normal logic, or it can be a group of logics working together, or it can be a group of logics applied on each other in a sequence.

The sequence thing is very interesting. In languages in which statements are not guaranteed to run in the same sequence they are written in code, we can pipeline execution of statements using a monad.

It should be noted when the code inside the Bind method would change. In each of the following, we are applying logic differently, means the logic of applying the logic is different, therefore in each of the following the code inside the Bind method have to be different:

  • Logging of method calls


  • Putting common logic of exception handling of method calls together


  • Taking in multiple functions, applying some on basis of result of another. It can be not applying some functions at all on basis of results of calling of other functions or varying number of types some functions are applied


  • Taking in multiple functions, and apply them one after another in a strict order

No comments:

Post a Comment