Saturday, November 21, 2015

Beautiful, Clean Mirrors in Dart with Reflectable


Tonight, I hope to make better Dart mirrors with the reflectable package. Better being a relative term, I hope to make my code clearer and the resulting compiled code smaller. Given my relatively small code, it may be asking too much of reflectable, but it always best to aim high.

I am still working on different approaches to the Flyweight Pattern for the forthcoming Design Patterns in Dart. The most recent tangent is exploring custom annotations that identify concrete flyweight classes in my code:
@flavor
class Cappuccino implements CoffeeFlavor {
  String get name => 'Cappuccino';
  double get profitPerOunce => 0.35;
}
The factory constructor responsible for caching flyweights uses this custom @flavor annotation to identify the Cappuccino class as a concrete flyweight that can be used in my coffee shop application.

Thanks to the built-in dart:mirrors library, I was able to get this working. Thanks to the @MirrorsUsed() annotation from dart:mirrors, I got this compiled to somewhat smallish Javascript. Let's see if reflectable can be better than "working" and "smallish."

I add reflectable to my pubspec.yaml:
name: flyweight_code
dependencies:
  reflectable: any
After a quick pub get to load the package and its dependencies, I replace the dart:mirrors import with reflectable in my coffee shop library:
library coffee_shop;

// @MirrorsUsed(metaTargets: "coffee_shop.Flavor")
// import 'dart:mirrors';

import 'package:reflectable/reflectable.dart';
// ...
With that, I get errors related to missing currentMirrorSystem method that had been defined in dart:mirrors. Let's see if I can fix that.

I start by replacing the const used for the @flavor annotation with a reflectable version:
class Flavor extends Reflectable {
  const Flavor()
    : super(newInstanceCapability);
}

const flavor = const Flavor();
I need the newInstanceCapability to replace the functionality in the factory constructor for CoffeeFlavor. With dart:mirrors that looked like:
class CoffeeFlavor {
  // ...
  factory CoffeeFlavor(name) {
    return _cache.putIfAbsent(name, () =>
        classMirrors[new Symbol(name)].
            newInstance('', []).
            reflectee
    );
  }
  // ...
}
This is the heart of the Flyweight Pattern that I am trying to replicate with reflectable. If a particular coffee flyweight is not present, this should add a single instance to the internal cache. Then, no matter how many times the same coffee flavor is ordered later, the same instance will be returned.

This code is not the most difficult aspect of using mirrors in this case. Finding the annotated classes to build that classMirrors property is a gigantic pain in dart:mirrors:
class CoffeeFlavor {
  // ...
  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]);
  // ...
}
Mercifully, the Reflectable class has just what I need: an annotatedClasses property. Since the Flavor constant used for the @flavor annotation is a subclass of Reflectable, I don't have to do any work to find the concrete, @flavor annotated, flyweight classes. I can simply use that property. All of that work then becomes just:
class CoffeeFlavor {
  // ...
  static Map classMirrors = flavor.
    annotatedClasses.
    fold({}, (memo, c) => memo..[c.simpleName]= c);
  // ...
}
Even nicer, the reflectable package makes it easier to get ahold of real objects—no need for the reflectee property. The factory constructor then becomes simply:
class CoffeeFlavor {
  // ...
  factory CoffeeFlavor(name) {
    return _cache.putIfAbsent(name, () =>
        classMirrors[name].
            newInstance('', [])
    );
  }
  // ...
}
That is a big win for overall readability of my mirror-based Dart code. Content, I stop here for today. I will pick back up with transforming this code into JavaScript tomorrow. And unless it generates gigabyte-sized code, I have the feeling that the reflectable package will become my go-to solution for mirrors in Dart. That is some pretty mirror code!


Day #10

No comments:

Post a Comment