Buckshot Videos

Loading...

Sunday, August 26, 2012

Working With Mirrors In Dart - A Brief Introduction

Mirrors provide a way to perform reflection on your application.  The great thing about mirrors vs traditional reflection in other languages is that they are not intrinsically tied to the object system.  This stratification provides many benefits.  If you want to learn more about mirrors in general, check out this great list of links provided by Gilad Bracha.

Dragons!  The mirror API is still very new and subject to change at any time.  At the time of this writing, it is only supported in the Dart VM (not dart2js).

A mirror is a meta object, that provides information about, and in some cases operations on, objects in a Dart application.  Each type of canonical "thing" in Dart has, generally speaking, a corresponding mirror type.

A listing of mirror types supported by the API.
As you can see from the list above, there is a mirror for just about everything.  Different mirrors provide different kinds of visibility (introspection) and operations.

Mirrors Beget Mirrors

Mirror functions always return another mirror as a result. Even the instantiation and invocation functions will return an InstanceMirror. It is from the InstanceMirror that you can get access to the concrete object from your application.

Perusing Dart With Mirrors

The mirror API allows you to inspect pretty much anything in your application.  At the top level, the starting point for doing so done this way:

 final MirrorSystem ms = currentMirrorSystem();  

I should note there that there is another sibling of currentMirrorSystem() called mirrorSystemOf(SendPort port), which is used to reflect on other isolates, but I won't be covering that in this post.

With this mirror in hand, you can iterate through all the libraries that are currently in scope of your application, iterating through the libraries map will naturally yield LibraryMirror's.

 final MirrorSystem ms = currentMirrorSystem();  
 // Iterating through libraries  
 ms  
   .libraries  
   .forEach((String name, LibraryMirror libMirror){  
      print('$name $libMirror');  
   });  

LibraryMirror, in turn, exposes collections of members, classes, functions, getters, setters, and variables for that particular library.  It becomes trivial then, to drill down to exactly information you need.

Instantiating and Invoking

There are two mirror types in particular that provide a way to instantiate objects and invoke methods:  ClassMirror and ObjectMirror.  In ClassMirror, there is a function called newInstance() which will return a Future<InstanceMirror> object. Lets see how this works:

 #import('dart:mirrors');   
   
 main(){   
  final foo = new Foo();   
   
  //lets get a reflection of foo   
  InstanceMirror fooIM = reflect(foo);   
   
  //now we get to the ClassMirror of fooIM   
  ClassMirror fooCM = fooIM.type;   
   
  //with our class mirror we can create a new instance of foo   
  //(an new InstanceMirror is returned)   
  fooCM   
   .newInstance('',[])   
   .then((InstanceMirror newIM){   
   
      //our concrete instance is now located in newIM.reflectee   
      newIM.reflectee.doSomething();   
   });   
  }   
   
 Class Foo   
 {   
   void doSomething(){   
     print('you did it!');   
   }   
 }   

Running this program will yield:

 you did it!  

Invoking methods is not much more difficult, but there is one area where you will want to pay special attention:  Invocation of instance methods will need to be done on an InstanceMirror, whereas invocation of static methods will be done on the ClassMirror.  Lets try both:

  #import('dart:mirrors');   
  main(){   
  final foo = new Foo();   
   
  //lets get a reflection of foo   
  InstanceMirror fooIM = reflect(foo);   
   
  //now we get to the ClassMirror of fooIM   
  ClassMirror fooCM = fooIM.type;   
   
  //invoke the instance method  
  fooIM.invoke('doSomething', [reflect('hello world')]);  
   
  //invoke the static method  
  fooCM.invoke('doSomethingStatic', []);  
  }   
   
  Class Foo   
  {   
   static void doSomethingStatic(){  
     print('you invoked me and I'm static method!');  
   }  
   
   void doSomething(String message){   
     print('instance method received message $message');   
   }   
  }   

.invoke() also returns a Future<InstanceMirror> on the result of the invocation, if we are interested in the return.  In the example above we aren't.

Also in the example above, we pass a value to doSomething() during the invocation, and we need to wrap it in a mirror using reflect() (the Dart team may do this automatically for us in the future).

Non-final fields, along with explicit getters and setters, also have a similar invocation mechanism, but the calls are made to .getField() and .setField() instead.  More information about these can be found in the ObjectMirror class.

Summary

The Dart mirrors API provides a powerful set of tools for reflecting on your code.  Hopefully this brief introduction to the will help you get started.

8 comments:

  1. Thx for this intro! keep up the good work with buckshot!

    ReplyDelete
  2. Thanks for doing all the leg work and providing this clear straight-forwards explanation.

    ReplyDelete
  3. cool :) No - How can i get a list of Class fields/properties without instance of Class ?

    ReplyDelete
  4. Hi j23tom. I assume what you ask is "how do I get to a ClassMirror without having to reflect a concrete object?"

    You do this by starting from a call to the currentMirrorSystem() function, which returns an object containing a map of all the libraries. You then query into the library for ClassMirror you are looking for (in the .classes map). From there you can query on fields (try the .variables map, for example).

    ReplyDelete
  5. What about regular functions? How I can execute a function? In the following code I derive a name for a function that exists. I just want to call it?

    final MirrorSystem ms = currentMirrorSystem();
    LibraryMirror artPen = ms.libraries['art_pen'];
    var seq = new Random().nextInt(999);
    var name;
    if (seq < 10) name = 'demo00${seq.toString()}';
    else if (seq < 100) name = 'demo0${seq.toString()}';
    else if (seq < 1000) name = 'demo${seq.toString()}';
    MethodMirror demo = artPen.functions[name];

    ReplyDelete
  6. Dzenan are you asking how to invoke a top-level library function? Static and instance functions are invoked via the ClassMirror and InstanceMirror, respectively.

    ReplyDelete