Tuesday, March 20, 2012

Dart Typedef

‹prev | My Chain | next›

While skimming through the Dart language specification tonight, I came across the entry for typedef. I am putting out a book on the subject in less than two weeks and I have no idea what typedef does, so let's rectify.

The spec is not terribly illuminating as to purpose, but it definitely applies to functions. I eventually grep through the dart-sdk source code and find a couple of examples. Typedefs seem to be used to constrain callbacks to a specific fingerprint.

Say, I could use that to limit the acceptable data sync callbacks in Hipster MVC. Currently, I allow any method to be injected into HipsterSync.sync=:
class HipsterSync {
  // private class variable to hold an application injected sync behavior
  static Function _injected_sync;

  // setter for the injected sync behavior
  static set sync(fn) {
    _injected_sync = fn;
  }
  // ...
}
To swap out the default data sync layer, I can, for example, use localStorage:
main() {
  HipsterSync.sync = localSync;
  // ...
}

localSync(method, model) {
  // localStorage awesomeness here
}
That works just fine, but I have no real way of ensuring (or at least strongly suggesting) that the function assigned via the HipsterSync.sync setter is of arity 2, returns a Future, and accepts a String as the first argument.

This is where typedef comes in handy.

In my HipsterSync library, I declare a SyncCallback typedef:
typedef Future<HashMap> SyncCallback(String _method, Dynamic model);
As a long time type hater, I am trying to resist the allure of that statement, but it's hard—so very hard to resist this temptation. Especially in my MVC library, that is a big deal. With that line, I declare that any callbacks need to return a Future callback—which will, in turn, supply a HashMap (i.e. the return value of a JSON.parse()). Moreover, the first argument needs to be a String (to describe the CRUD operation). I am lax with the second argument because it can be either a HipsterModel or a HipsterCollection (I should really create a common interface for the two).

With that, I can declare that my _injected_sync function needs to be a "SyncCallback":
typedef Future<HashMap> SyncCallback(String method, Dynamic model);

class HipsterSync {
  // private class variable to hold an application injected sync behavior
  static SyncCallback _injected_sync;

  // setter for the injected sync behavior
  static set sync(SyncCallback fn) {
    _injected_sync = fn;
  // ...
}
And, if I give my localStorage sync method an arity of 3 instead of SyncCallback's 2:
main() {
  HipsterSync.sync = localSync;
  // ...
}

localSync(method, model, asdf) {
  // localStorage awesomeness here
}
Then I get a nice exception:


That is a type-checked mode exception only, but it is a compile time error, ensuring that I will know that there is a problem immediately. No test suite run, no smoke tests. Nice.

As a long time dynamic programmer, I really want to dislike this, but I cannot find good cause. Even if I can come up with a good excuse to hate, it is not as if Dart is forcing me to use this feature—I didn't even know about it until tonight. Dart is really shaping up to be a gateway drug for strongly typed programming. Dammit.


Day #331

No comments:

Post a Comment