Last week I wrote about per-instance meta-classes and ExpandoMetaClass DSL. At that point it was just prototyped code in my working copy and I was not even sure if it will come to Groovy 1.6 trunk. Now it is there. If you didn’t read this previous post I recommend to have quick look on it as it might help to understand current one.
Today I want to talk about another part of the same commit, which is kind of side-effect of ability to have per-instance meta classes. Now we able to have per instance dynamic stateless mixins.
But let me start we explanation of what is mixin for me.
Mixin is either static or dynamic way to extend class or instance behavior by adding methods and/or state defined in another class.
Static here means defined at compile time and dynamic means defined on runtime. This two types of mixins are totally different.
- Static mixins applied to class but dynamic one to meta class (either class one or instance one)
- Static mixins always effect hierarchy, dynamic ones may or may not effect it depending on nature of meta class under effect (if meta class is the one associated with the class it does, if not it does not
- Static mixins always effect type information, dynamic ones never does that.
Stateless or stateless mixins means ability of mixins to extend object behavior not only with methods but with state as well. Stateful compile time mixins can be understand as kind of multiple inheritance.
Groovy has no compile time mixins yet. I think Groovy has to have something similar to Scala traits but it is subject to different discussion.
Groovy has no stateful mixins yet as well and below I will talk a bit about my idea how they should look like. So main subject for today is dynamic stateless mixins
Let me show simple example. First of all we define usual category class, which will be used to mix in.
class DeepFlattenToCategory {
static Set flattenTo(element) {
LinkedHashSet set = new LinkedHashSet()
element.flattenTo(set)
return set
}
// Object - put to result set
static void flattenTo(element, Set addTo) {
addTo <
element.flattenTo(set);
}
}
// Map - flatten each value
static void flattenTo(Map elements, Set addTo) {
elements.values().flattenTo(addTo);
}
// Array - flatten each element
static void flattenTo(Object [] elements, Set addTo) {
elements.each { element ->
element.flattenTo(set);
}
}
}
This category does pretty obvious thing – deep flattening of complex data structure as demonstrated by following code snippet.
// mixin Category to meta class
Object.metaClass.mixin DeepFlattenToCategory
// and here we are
assertEquals ([8,9,3,2,1,4], [[8,9] as Object [], [3,2,[2:1,3:4]],[2,3]].flattenTo () as List)
Careful reader may notice probably that we modify only meta class for Object but methods for Collection, Object [] and Map becomes available as well. Such behavior is usual for categories but a bit uncommon for ExpandMetaClass – in the past he had to call ExpandoMetaClass.enableGlobally () to ask meta classes to look for missed methods. Such approach had several downsides – to name a few: unnecessary memory footprint for creation ExpandoMetaClass for each class (both modified and non-modified) and lack of “good place” where to call ExpandoMetaClass.enableGlobally (). It means that if you developed library, which requires it – you should call it somewhere, because nobody can guarantee that user application will do that. Fortunately, now ExpandoMetaClass can define methods for sub classes and if sub class missed some method he will try to find it in hierarchy. Obviously such behavior is must for mixins – otherwise our sample will look much less nicer(which is still to be valid code but creating four ExpandoMetaClasses instead of one)
// mixin Category to meta classes
Object.metaClass.mixin DeepFlattenToCategory
Collection.metaClass.mixin DeepFlattenToCategory
Object[].metaClass.mixin DeepFlattenToCategory
Map.metaClass.mixin DeepFlattenToCategory
// and here we are
assertEquals ([8,9,3,2,1,4], [[8,9] as Object [], [3,2,[2:1,3:4]],[2,3]].flattenTo () as List)
Object.metaClass.foo(ArrayList){ -> .... }
BTW, ExpandoMetaClass has new syntax to define methods for subclasses
Object.metaClass.foo(ArrayList){ -> .... }
OR
Object.metaClass {
foo(ArrayList){ -> .... {
}
OR
Object.metaClass {
define(ArrayList){
foo{ -> .... }
}
}
OK, back to mixins. So far, it seems very similar to normal categories, so let us make our sample more interesting
class NoFlattenArrayListCategory {
// ArrayList - put to result set
static void flattenTo(ArrayList element, Set addTo) {
addTo << element;
}
}
def x = [2,3]
x.metaClass.mixin NoFlattenArrayListCategory
assertEquals ([x, 8,9,3,2,1,4], [x, [8,9] as Object [], [3,2,[2:1,3:4]],[2,3]].flattenTo () as List)
What we do here is create category, which does not flatten array lists and mix it in instance meta class. Voila!
Of course the same can be achieved with ExpandoMetaClass without mixing in category
def x = [2,3]
x.metaClass.flattenTo = { Set addTo -> addTo << delegate }
assertEquals ([x, 8,9,3,2,1,4], [x, [8,9] as Object [], [3,2,[2:1,3:4]],[2,3]].flattenTo () as List)
but please remember that category can be easily implemented in Java and provide much better performance compare to closure-based meta methods. And now we can use it as mixins.
The last thing I want to talk about is some features, which is missing and which I would love to see in Groovy. All below is just imagination and I don’t know exact implementation details and underwater stones but here are problems to be addressed.
- Stateful mixins
- Mixing in objects
- Better syntax to define categories in Groovy
- Compile time mixins
Stateful mixins
Imagine that category class defines not only static methods but also some fields and these fields magically becomes available as properties when we mix these category in. Probably if object already has such properties the one defined by category should be ignored, but if not… we have extremely powerful tool.
The main technical problem here is that we don’t want to define these new properties via ExpandoMetaClass, which will be very slow, but if we created instance of category class to associated it with original object… well, we have no good place to store this association. For Groovy compiled objects we of course can store refernence(s) directly in the object and for Java one in WeakHashMap. But what will happen if this mixed in instance keeps strong reference for original Java object… It will never be collected. Believe me, it will happen very often. So far I don’t know any good solution for that.
Mixing in objects
If we had solution for stateful mixin most probably it will lead us to ability to mix in not only category class but any object. I can imagine fantastic possibilities for that.
Better syntax to define categories in Groovy
Well static methods with additional parameter ‘self’ are so ugly. We definitely need something better. So far we have @Category annotation, which solves only part of the problem.
Compile time mixin
Having stateful mixins with good syntax to define category methods will lead us to ability mixin category classes on runtime. As result we will have probably the strongest mixin story between all dynamic languages and as powerful as Scala traits in the world of staticly compiled ones.
So it goes.
You must be logged in to post a comment.