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.