Tuesday 3 January 2012

Monax - crossplatform Monads via haXe.

Monax


Motivation


If, like me, you code on NodeJs; you may find it is easy to make spaghetti code. Even properly written, a library smell too much..

Another problem is how to make sure you did not forgot a use case while writting your code.. ?
It would be nice to have a rock solid safe code composition capability; requiering less test code.

Every seasonned statically typed functionnal programmer will tell you to build up an abstraction for that and I'm pretty sure they mean Monads!
I really invite you to read more about them.

Now, that's a good idea but not all languages provides the so usefull syntactic sugar that make Monads really very clean to read / write.

I've missed too much Monads in haXe so I've decided to unleash macro capabilities to get them working - now, it is here, for your pleasure:

Monax on gitHub!


Introducing the Monax macro library


This library, built on top of haXe macros give the user the possibility to define specific Monad instance and get automatically access to a haskell like sugared syntax.
About monad: Monads on Wikipedia

The syntactic sugar rely on the existence 'ret' and 'flatMap' in the Monad instance.
It also requiers the existence of 'map' to provide automatic optimisations.


Learn the (not so) hard way - Defining a Monad instance.


class OptionM {
    
  @:macro public static function dO(body : Expr) return // the function to trigger the Monad macro.
    Monad.dO("OptionM", body, Context)

  inline public static function ret(x : T) return // creates an element
    Some(x)
  
  inline public static function map < T, U > (x : Option, f : T -> U) : Option {
    switch (x) {
      case Some(x) : return Some(f(x));
      default : return None;
    }
  }

  inline public static function flatMap(x : Option, f : T -> Option) : Option {
    switch (x) {
      case Some(x) : return f(x);
      default : return None;
    }
  }
}

Using a Monad instance (Option here)


OptionM.dO({
          value <= ret(55);
          value1 <= ret(value * 2);
          ret(value1 + value);
        });


Due to optimisations; this code reduce to Some(165), those optimisations are applied by default.
One can define his own optimisations (feeding the Monad.dO method its last parameter)

@:macro public static function dO(body : Expr) return
    Monad.dO("OptionM", body, Context, myCustomTransformation)

(please refer to the default function code to make sense of how to make optimization - macro knowledge requiered).

or using no optimisation at all:

@:macro public static function dO(body : Expr) return
    Monad.dO("OptionM", body, Context, Monad.noOpt)


Next..


Now I've this up and running; I plan to release a packrat parser combinator which is waiting on my github - accompagnied with his own Monad instance.

Later, I'll also release some abstractions for NodeJs.

No comments:

Post a Comment