Thursday, May 8, 2014

Reflecting on Annotations


Last night's exploration of annotations in Dart piqued my curiosity over how Angular.dart uses annotations so effectively.

To date, I have only used annotations as glorified code comments. But there is clearly great potential that I am overlooking. So I start with a simple main.dart file that contains a main entry point with an annotation:
@meta('This is main!')
main() {
  print('[main] Hello!');
}
Simple enough, when run from the command-line, it prints out:
$ dart main.dart 
[main] Hello!
It fails dartanalyzer analysis without defining that annotation:
$ dartanalyzer main.dart
Analyzing main.dart...
[error] Annotation can be only constant variable or constant constructor invocation (/home/chris/repos/gists/reflecting_annotations/main.dart, line 5, col 1)
1 error found.
So I define a simple “meta” annotation class:
/// Sweet meta data about a class (or thing)
///
///     @meta('Nice code')
///     class Bar { /* ... */}
class meta {
  final String note;
  const meta(this.note);
}
That satisfies dartanalyzer while my code still runs as before.

That's all well and good, but how do I get at that annotation data? For that, I am going to need to start with the dart:mirrors library:
import 'dart:mirrors';

@meta('This is main!')
main() {
  print('[main] Hello!');
}
Now I need to do something with those mirrors. I have to confess that I find this a non-obvious path from this point. I know that I need to start by reflecting on main():
  var selfMirror = reflect(main);
Reading through Angular.dart source, I know that I need to extract metadata, but it is not entirely clear to me how it does this. What is clear is that metadata is a property of DeclarationMirror. But reflect() is giving me a ClosureMirror, which does not have a DeclarationMirror property or method. After a bit of meandering, I find that ClosureMirror has a function property which just so happens to implement DeclarationMirror.

I can get that meta data (for the @meta annotation which I would have named differently had I known the name of the getter) as a list of InstanceMirror objects. I access just the first and print it out:
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]}.');
}
Which gives me:
$ dartanalyzer main.dart; dart main.dart
Analyzing main.dart...
No issues found
[main] Hello!
[main] Meta is: InstanceMirror on Instance of 'meta'.
I try accessing the note field from my meta class:
  print('[main] Meta is: ${metaData[0].getField(#note)}.');
That gets me the actual notation that I wanted, but I am still working with an object instead of the actual string note:
[main] Meta is: InstanceMirror on "This is main!".
To get the note property, I access the relfectee property of the meta data (which is an InstanceMirror):
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}');
}
That finally gets me access to the value that I wanted from the annotation:
$ dartanalyzer main.dart; dart main.dart
Analyzing main.dart...
No issues found
[main] Hello!
[main] Meta is: This is main!
I probably have to work with this mirror stuff more to appreciate it. Right now, I am going from ClosureMirror to MethodMirror to InstanceMirror to “reflectee”—all of which seems like quite a journey.

Thinking about it, I can understand the InstanceMirror / “reflectee” relationship. The former has to hold any number of different types of objects (reflectees) in a consistent API, but also allow direct access to the actual object.

I also get the MethodMirror / InstanceMirror relationship. The meta data is made available as a list of these instance mirrors, which it can build via the mirror class definition.

I am not sure that I fully understand the difference between ClosureMirror and MethodMirror. The latter, as a class that implements DeclarationMirror, would seem to have more to do with the language. The ClosureMirror, which implements InstanceMirror would seem to have more to do with the running of the code. I suppose I begin to understand the difference, but it feels like there are some bigger concepts that I would only fully grasp with more experience. Which I may try to get more of tomorrow.



Day #57

No comments:

Post a Comment