Saturday, February 11, 2012

Dart's noSuchMethod Feature

In Dart, a call to a non-existent method on an object will be routed to a special method named noSuchMethod(..) which all objects inherit from Object. For example, if you run this code:

p1.jump();

and p1 doesn't have a method named jump then Dart will instead call:

p1.noSuchMethod(..)

The default implementation of noSuchMethod (the one from Object) just throws a NoSuchMethodException, which is typically what you want. But you can override noSuchMethod to some useful things. First, I'll show you a trivial example, to illustrate the basic mechanics then I'll show a slightly more practical use-case.

Here is the simplest possible example:

class Box{
  noSuchMethod(InvocationMirror invocation) {
    print(invocation.memberName);
  }
}

main(){
  var b = new Box();
  b.foo(); //prints foo, the method name
}

Since b does not have a foo method, it calls our noSuchMethod method. This is obviously not very useful. Below is a more practical example.

A more realistic use-case
Many developers like to access and update database records as objects. This is what Java's Hibernate and Rail's Active Record and Django's Object-relational mapper do. One way to do this is to wrap a generic map or database record with a class as in the example below:

class Person{
  
  DbRecord record;
  
  String get firstName() => record.getFieldValue("firstName"); 
  String get lastName() => record.getFieldValue("lastName"); 
  String get age() => record.getFieldValue("age"); 
  String get fullName() => firstName + " " + lastName; 
  
}

The thing to notice here is that 3 of the 4 getter methods are completely generic. Here is how this could be made easier using noSuchMethod:

class Person{

  DbRecord record;

  String get fullName() => firstName + " " + lastName; 

  noSuchMethod(InvocationMirror invocation) => record.getFieldValue(invocation.memberName);
}

By extracting this into a generic base class it become a bit more useful.

Like many of Dart's features, this is not something ground breaking. Dart's nosuchMethod capability is similar to Ruby's method_missing and Python's __setattr__.



1 comment:

Roman said...

Dave, I keep getting "cannot resolve class name 'InvocationMirror' from Model" while trying to run this code, any ideas?

class Model {

Map fields;

Model(Map new_fields) {
new_fields.forEach((k,v) => fields[k] = v);
}

noSuchMethod(InvocationMirror invocation) {
if(fields.containsKey(invocation.memberName)) {
return fields[invocation.memberName];
} else {
super.noSuchMethod(InvocationMirror invocation);
}
}

}

It looks like InvocationMirror is not implemented yet. How so? I downloaded Dart's SDK for Mac from the official site. Is this somewhat an older version, which doesn't have this class?