Friday, May 9, 2014

Reflecting on Dart Classes and Variables


I have the beginnings of understanding for reflection in Dart.

Coming from a predominantly dynamic language background, I confess that I find most of this painful and awkward. Still, there is something fascinating about it—and not just in a gawking-at-a-car-crash kind of way (though there is definitely some of that). Even if I stick mostly to dynamic languages, this stuff will give me a better appreciation for what is going on. But as Anguar.dart has proven, there is something inherently useful about this by itself.

I left off last night with the ability to reflect on annotations on the current function:
import 'dart:mirrors';

@meta('This is main!')
main() {
  print('[main] Hello!');

  var selfMirror = (reflect(main) as ClosureMirror).function;
  var metaData = selfMirror.metadata;
  print('[main] Meta is: ${metaData[0].reflectee.note}');
}

// The @meta annotation definition
class meta {
  final String note;
  const meta(this.note);
}
The above prints out the following when run from the command-line:
[main] Hello!
This foo is Bob.
[main] Meta is: This is main!
What I would like tonight is to figure out how to reflect on class metadata (i.e. annotations). Just to make things interesting, I move my annotation class and the class on which I would like to reflect into a new code file, foo.dart:
@meta('Foo should not be used in real life')
class Foo {
  String name;
  Foo(this.name);
  toString() => "This foo is $name.";
}

class meta {
  final String note;
  const meta(this.note);
}
Back in main.dart, I import foo.dart, create and use an instance of the imported Foo to make sure it works, and then reflect on the Foo class:
import 'foo.dart';
import 'dart:mirrors';

@meta('This is main!')
main() {
  print('[main] Hello!');

  var selfMirror = (reflect(main) as ClosureMirror).function;
  var metaData = selfMirror.metadata;
  print('[main] Meta is: “${metaData[0].reflectee.note}”');

  var foo = new Foo('Bob');
  print(foo.toString());

  var fooMirror = reflect(Foo);
  print(fooMirror);
}
This prints out the following when run:
$ dartanalyzer main.dart && dart main.dart
Analyzing main.dart...
No issues found
[main] Hello!
[main] Meta is: “This is main!”
This foo is Bob.
InstanceMirror on Type: class 'Foo'
So the toSting() method on Foo seems to work just fine. That is not the problem that I am trying to solve here. What I want is a way to extract metadata from the code. As I found last night, that InstanceMirror that I get by reflecting on Foo is not going to help me. I need something that implements a DeclarationMirror. For last night's function, that was a MethodMirror. For classes, that would appear to be a ClassMirror, but I can find no way to get to a ClassMirror from the reflected InstanceMirror.

The answer turns out to be that I should not be reflecting on the Foo class. Instead I should be reflect-classing on the Foo class with with reflectClass() function:
main() {
  // ...
  var fooMirror = reflectClass(Foo);
  var fooMetaData = fooMirror.metadata;
  print('[main] Foo meta is: “${fooMetaData[0].reflectee.note}”');
}
With that, I am successfully able to grab the note from the @meta annotation:
$ dartanalyzer main.dart && dart main.dart
Analyzing main.dart...
No issues found
[main] Hello!
[main] Meta is: “This is main!”
This foo is Bob.
[main] Foo meta is: “Foo should not be used in real life”
To summarize, to grab metadata from a function, I reflect on the function, access the function property from the ClosureMirror to get a MethodMirror, which contains the metadata. To grab metadata from a class, I reflect-class on the class, which gives me direct access to the metadata. I do not have high hopes that I am going to remember that next week.

Because that is not confusing enough, I find myself wondering about variable declaration annotations. So, what the heck, I annotate the name instance variable in my Foo class with my same custom @meta annotation:
@meta('Foo should not be used in real life')
class Foo {
  @meta('This is the name of the Foo')
  String name;
  Foo(this.name);
}
Getting this meta information at runtime turns out to be fairly easy. Well, maybe not “fairly”—let's call it “relatively” easy. The ClassMirror from reflectClass() has a declarations Map. I can iterate over that to get any declarations that contain annotation metadata with:
main() {
  // ...
  var fooMirror = reflectClass(Foo);
  var fooMetaData = fooMirror.metadata;
  print('[main] Foo meta is: “${fooMetaData[0].reflectee.note}”');

  for (var k in fooMirror.declarations.keys) {
    if (fooMirror.declarations[k].metadata.length == 0) continue;
    var _m = fooMirror.declarations[k].metadata;
    print("[main] $k: ${_m[0].reflectee.note}");
  }
}
I rather like that. It is nice that the ClassMirror has such nice information readily available. I'm not saying that I am going to drop all my dynamic languages or anything, but reflecting on variables makes merciful sense to me.

Day #58

No comments:

Post a Comment