Monday, November 23, 2015

Building Reflectable Dart Code with a Dependency Assist


OK, let's try that again.

Yesterday I was unable to build my Reflectable-based Dart code—either into fully assembled Dart or into JavaScript. Thanks to a hint from Sigurd Meldgaard in the comments, I am ready.

I am still exploring approaches to the Flyweight Pattern for Design Patterns in Dart. I have already more or less settled on a simple reflection approach, but am exploring code annotations for marking concrete flyweight classes, which is where Reflectable enters the story. And almost exited yesterday—until Sigurd told me that Reflectable currently requires an overridden dependency on the analyzer package.

So I add the appropriate dependency_overrides entry to my pubspec.yaml file:
name: flyweight_code
dependencies:
  reflectable: any
dependency_overrides:
  analyzer: '0.26.1+14'
transformers:
- reflectable:
    entry_points:
      - bin/coffee_orders.dart
    formatted: true
With that... everything works. My code builds without issue:
$ pub build --mode=debug bin          
Loading source assets... 
Loading reflectable transformers... 
Building flyweight_code... (3.8s) 
[Info from Dart2JS]:
Compiling flyweight_code|bin/coffee_orders.dart...
[Info from Dart2JS]:
Took 0:00:04.819196 to compile flyweight_code|bin/coffee_orders.dart.
[Info from Dart2JS]:
Compiling flyweight_code|bin/coffee_orders_reflectable_original_main.dart...
[Info from Dart2JS]:
Took 0:00:01.456035 to compile flyweight_code|bin/coffee_orders_reflectable_original_main.dart.
Built 589 files to "build".
The compiled code is compact:
$ ls -lh build/bin/coffee_orders.dart*
-rw-r--r-- 1 chris chris 5.0K Nov 24 00:17 build/bin/coffee_orders.dart
-rw-r--r-- 1 chris chris 100K Nov 24 00:17 build/bin/coffee_orders.dart.js
By way of comparison, the same code based on the built-in dart:mirrors library was nearly 300K the other night. Sure, I am still compiling 5K of Dart into 100K of JavaScript, but that's a nearly 66% improvement.

Of course, what matters most is that the code actually work, which it does. Running the compiled JavaScript in Node results in my usual coffee shop output:
$ node build/bin/coffee_orders.dart.js
Served Cappuccino to Fred.
Served Espresso to Bob.
Served Frappe to Alice.
Served Frappe to Elsa.
Served Coffee to null.
Served Coffee to Chris.
Served Mochachino to Joy.
-----
Served 7 coffee drinks.
Served 5 kinds of coffee drinks.
For a profit of: $27.2
The verdict here is that Reflectable is a huge win all around. The minor, temporary issue of a out-of-sync dependency aside, Reflectable is simply better than dart:mirrors. The resulting generated code is smaller. It is easier to build. From my perspective, the biggest win is that it makes my code much cleaner.

To find classes annotated with the custom @flavor annotation in dart:mirrors, I had to traverse libraries and declarations. It was ugly:
  static Map _allDeclarations = currentMirrorSystem().
      libraries.
      values.
      fold({}, (memo, library) => memo..addAll(library.declarations));

  static Map classMirrors = _allDeclarations.
    keys.
    where((k) => _allDeclarations[k] is ClassMirror).
    where((k) =>
      _allDeclarations[k].metadata.map((m)=> m.type.reflectedType).contains(Flavor)
    ).
    fold({}, (memo, k) => memo..[k]= _allDeclarations[k]);
The equivalent code with Reflectable is mercifully easy to read:
  static Map classMirrors = flavor.
    annotatedClasses.
    fold({}, (memo, c) => memo..[c.simpleName]= c);
The Reflectable @flavor annotation just knows the classes that it annotates. What could be easier?

That said, I am not sold on annotations as the best approach to concrete flyweight classes in Dart. I may try my original, not-annotated solution again—but with Reflectable. It could be that Reflectable is optimized for annotations, but other cases still work equally or more well in dart:mirrors. I will find out with at least one other case. Tomorrow.


Day #12

3 comments:

  1. Hi Chris,
    we just published version 0.3.4 of reflectable, which includes a rather long list of bug fixes and also the version constraint that Sigurd mentioned. So you should be able to do `pub upgrade` to get it (after deleting the 'dependency_overrides' directive from your 'pubspec.yaml'). That way you'll also get the newest version of the analyzer automatically when the stricter-than-normal version constraint in reflectable is removed.

    ReplyDelete
  2. One more thing came to mind when I took a brief look at https://dartpad.dartlang.org/c7dabc0c57a93e8d88d7: Did you consider putting `@flavor` only on `class CoffeeFlavor`, and then using `subtypeQuantifyCapability`?

    ReplyDelete
    Replies
    1. I had not considered using `subtypeQuantifyCapability` -- I'll give it a go tonight or tomorrow. Thanks!

      Delete