Wednesday, July 2, 2014

@MirrorsUsed Meta Targets


I have to admit it. This took me a little while to figure out.

Last night I was able to apply the @MirrorsUsed annotation to some mirror-based Dart code, thus reducing the size of dart2js compiled code from 1.6MB to 170kb. The solution worked, but was not practical for the particular use I am exploring. So tonight, I figured out how to get @MirrorsUsed to do a little more.

The code in question is a Factory Method pattern implementation. I already have a couple of other approaches as well, but being a goofball for dynamic language stuff, I cannot resist mirrors, even if they are terribly slow. But I would prefer my code not be slow and incredibly large, which is where @MirrorsUsed comes into the picture.

Last night's approach specified the list of all classes that my code reflects upon. Since this is sample code, I only reflect on one class, ConcreteProduct (a name describing the classes role in the pattern, not an actual implementation). Since there is only one class that gets reflected, the @MirrorsUsed annotation looks like:
@MirrorsUsed(targets: 'ConcreteProduct')
import 'dart:mirrors';
Easy-peasy.

Except it will not work in the typical Factory Method pattern case of frameworks. In a framework, the base classes are in one package and libraries are usually in a separate packages. My base Creator class (that would reside the in framework package) is the one that does the reflecting:
abstract class Creator {
  Type productClass;

  Product productMaker() {
    return reflectClass(productClass).
      newInstance(const Symbol(''), []).
      reflectee;
  }
}
The concrete creator class that defines the productClass instance variable and the concrete product class would both reside in a separate package:
class ConcreteCreator extends Creator {
  Type productClass = ConcreteProduct;
}
class ConcreteProduct extends Product {}
The problem is that the import occurs in the “framework” package, which means that the @MirrorsUsed annotation would have no way to know which class the concrete application classes will specific for reflection.

This turns out the be the province of the metaTargets option to @MirrorsUsed. The targets option points to a list of classes that will be reflected. The metaTargets, on the other hand, are a list of code annotations that will identify classes that receive reflection.

This means that I have to write my own annotations. So I define a @product annotation as:
const FactoryProduct product = const FactoryProduct();
class FactoryProduct {
  const FactoryProduct();
}
I then tell the dart:mirrors package that it needs to reflect classes annotated with @product with the metaTargets option:
@MirrorsUsed(metaTargets: const[FactoryProduct])
import 'dart:mirrors';
Lastly, I annotate my concrete (application) class that will be reflected:
@product
class ConcreteProduct extends Product {}
With that, I can compile (relatively) small JavaScript:
$ dart2js -o tool/benchmark.dart.js tool/benchmark.dart
$ ls -lh tool/benchmark.dart.js
-rw-r--r-- 1 chris chris 171K Jul  2 23:03 tool/benchmark.dart.js
And my JavaScript benchmarking code still runs under Node:
$ node tool/benchmark.dart.js                          
Factory Method — Subclass(RunTime): 0.09583097409453985 us.
Factory Method — Map of Factories(RunTime): 4.2090907942975235 us.
Factory Method — Mirrors(RunTime): 7.62668883490888 us.
In the end, it was not too tricky to get this working. It just took me a while to realize that annotations were the order of the day.

That will do for my initial investigation into the Factory Method for Design Patterns in Dart. Up tomorrow, I switch to a behavioral Design Pattern that I have always hated.

Day #110

1 comment:

  1. "I switch to a behavioral Design Pattern that I have always hated."

    You mean the spaghetti pattern :-P

    ReplyDelete