Saturday 22 October 2011

Compile time generation of Lenses in haxe.

After my previous work around externs generation; I started to think about something that worried me a lot while programming in haXe (it also applies to other languages).


let me put it simply with this snippet:

typedef Toto = {
    var name : String
  }

  var totoNames = arrayOfTotos.map(function (x) return x.name);

As a programmer I am lazy like hell (I often think it's a quality).
I would like to be able not to write the function, I want it to be generated for me automatically so I may write:

var totoNames = arrayOfTotos.map(User_.name_.get);

Also it makes sense to provide a setter the same way we provided a getter.

something like:

arrayOfTotos.map(callback(User_.name_.set, "georges"));

(note that callback is a way to perform partial application in haxe; I personnaly think a special, more general syntax would bring more awesomeness, but at least, we have it :) )


What we've just defined is in fact a Lense.

Here's the actual Lense signature in haXe :

typedef Lense < A, B > = {
    get : A -> B,
    set : B -> A -> A
  }

One interesting thing with Lenses is that we can combine them with some combinators, so let's define some extensions:

class LenseExtension {

    inline public static function mod< A, B >(l1 : Lense < A, B > , f : B -> B, a : A) : A  return
      l1.set(f(l1.get(a)), a)

    public static function andThen < A, B, C > (l1 : Lense < A, B > , l2 : Lense < B, C > ) : Lense < A, C > return {
      get : function (a : A) return l2.get(l1.get(a)),
      set : function (c : C, a : A) : A return
              mod(l1, function (b) return l2.set(c, b), a)
    }

    inline public static function compose < A, B, C > ( l2 : Lense < B, C >, l1 : Lense < A, B > ) : Lense < A, C > return andThen(l1, l2) 
  }

With this we can then compose and build some interesting abstractions:

given this:

var userA : User = {
    age : 6,
    name : "georges",
    say : function (x) return "Beh..." + x
  };

  var userGroup :  UserGroup = {
    users : [userA, userA],
    lead : userA,  
  };

We can do that for instance :

var ageOfUserGroupLead = UserGroup_.lead_.andThen(User_.age_);

and use it like that:

var newGroup = ageOfUserGroupLead.set(5, userGroup);

the interesting bits is that newGroup is structurally equivalent to userGroup but its lead age is modified, in fact all datas are the same but the modified parts; It's an instance of immutable structure 'update'.

This reveals to be particularly helpful when coding against types which have a lot of fields.

Providing an additional, zero cost extension can brings some syntactic sugar:

class LenseSyntactic {

    inline public static function set< A, B >(a : A, l : Lense< A, B >, v : B) : A return
l.set(v, a)

    inline public static function get< A, B >(a : A, l : Lense< A, B >) : B return
l.get(a)
  }

so that you can now write this:

userGroup.set(ageOfUserGroupLead, 5);

It is quite clean as an instance of declarative programming.

Here is how the Lenses are built:

import com.mindrocks.macros.Lense;
  using com.mindrocks.macros.Lense;

  class User_ implements LensesFor< User > {}
  class UserGroup_ implements LensesFor< UserGroup > { }

can't be simpler..


Note than you also have the ability to provide a modification through a function you may apply to a lense (syntactic sugar version):

function complexUserModification(user) {
      var newUser : User = Reflect.copy(user);
      // do you complex stuff
      return newUser;
    }
    
    userGroup.mod(UserGroup_.lead_, complexUserModification);


Performance wise it is comparable to an hand written factorised version; we can provide a lot of speed if/when a limitation of inlining, namely the impossibility to inline functions containing closures is relaxed and another optimization involving removal of temporary unused objects ('escape analysis'?) is done - if possible.

Anyway, for those who want to favor coding speed and then optimize on need; this is a valuable tool.

The code is accessible on my misnamed github repo:
https://github.com/sledorze/haxeExtensionBuilder

Feel free to comment!


P.S.: Specific (think containers) Lenses could/would/will? be generated by hand in a near? future.

No comments:

Post a Comment