Monday, February 27, 2012

noSuchMethod in Dart

‹prev | My Chain | next›

While writing the dynamic behaviors chapter in Dart for Hipsters, it occurred to me that I had skipped right over the noSuchMethod() method in my exploration of Dart. So tonight I revisit my Hipster MVC library to see if I can vary between local a remote storage with noSuchMethod().

I have yet to even try noSuchMethod(), but my guess is that it serves the same purpose as method_missing() in Ruby. That is, if a method is not explicitly defined in a class or any of its super classes, then noSuchMethod() should be invoked with the name of the method and the list of arguments. Furthermore, as a method on Object, it should be inherited by all classes. Then again, I could be completely off-base.

I glossed over the topic in Dart for Hipsters, but left the following psuedo-code as placeholder:
class HipsterModel {
  // ...
  noSuchMethod(name, args) {
    if (useLocal()) {
      _localSave(args);
    }
    else {
      _defaultSave(args);
    }
  }
}
Even if my assumptions are correct, I already see a mistake in that code—I am not checking the method name. It should only act if the method is expected, otherwise it ought to throw an exception:
class HipsterModel {
  // ...
  noSuchMethod(name, args) {
    if (name != 'save') {
      throw new NoSuchMethodException(this, name, args);
    }

    if (useLocal()) {
      _localSave(args);
    }
    else {
      _defaultSave(args);
    }
  }
}
To get there, I first rename save() as _defaultSave() and add a simplified noSuchMethod():
class HipsterModel implements Hashable {
  // ...
  noSuchMethod(name, args) {
    if (name != 'save') {
      throw new NoSuchMethodException(this, name, args);
    }

    _defaultSave(args);
  }

  _defaultSave([callback]) {
    var req = new XMLHttpRequest()
      , json = JSON.stringify(attributes);

    req.on.load.add((event) {
      attributes = JSON.parse(req.responseText);
      on.save.dispatch(event);
      if (callback != null) callback(event);
    });

    req.open('post', '/comics', true);
    req.setRequestHeader('Content-type', 'application/json');
    req.send(json);
  }
}
And that works. But not really.

It works in the sense that I do not see my NoSuchMethodException. It also works in the sense that _defaultSave() is invoked. Those two small victories mean that my understanding of noSuchMethod() is more or less correct—it is invoked if Dart is otherwise unable to resolve the requested method.

Things go awry inside the on.load event listener. After the record is saved via XHR, the event listener attempts to invoke the optional callback, with less than ideal results:
Exception: Object is not closure
Stack Trace:  0. Function: 'HipsterModel.function' url: 'http://localhost:3000/scripts/HipsterModel.dart' line:39 col:37
 1. Function: 'EventListenerListImplementation.function' url: 'dart:htmlimpl' line:23128 col:35
Ah, "Object is not closure", you're so helpful, you might as well say "Something went wrong".

What I suspect went wrong is how I invoked _defaultSave() from noSuchMethod(). I called _defaultSave(args) which I suspect is rather like calling _defaultSave() with a List.

The documentation for noSuchMethod says that args is a List, but I am calling save with optional parameter syntax:
    // ...
    new_model.save(callback:(event) {
      this.add(new_model);
    });
    // ...
What does Dart do with the optional label (callback in this case)? Since the result is a List, I can only assume that it ignores the optional parameter label, so I grab the first argument in noSuchMethod() and use that as the optional callback parameter to _defaultSave():
  noSuchMethod(name, args) {
    if (name != 'save') {
      throw new NoSuchMethodException(this, name, args);
    }

    _defaultSave(callback: args[0]);
  }
That does the trick—the Ajax save now works:


Armed with that knowledge, I can implement a stateful save() in noSuchMethod():
class HipsterModel implements Hashable {
  // ...
  bool useLocal() => true;

  noSuchMethod(name, args) {
    if (name != 'save') {
      throw new NoSuchMethodException(this, name, args);
    }

    if (useLocal()) {
      _localSave(callback: args[0]);
    }
    else {
      _defaultSave(callback: args[0]);
    }
  }

  _defaultSave([callback]) { /* ... */ }
}
Since useLocal() is hard-coded to true, I give it a try and, indeed, I am storing locally:


I am now well-armed to write the noSuchMethod() section. That said, the current usefulness is limited to methods with fixed arity. Optional parameters in Dart boast that position does not matter: new Comics(el: '#comics', collection: my_comics) is the same as new Comics(collection: my_comics, el: '#comics'). This would definitely not be the case with dynamic methods implemented by noSuchMethod. Whether this is by design to limit dynamic methods or if a newer implementation is on the way, I cannot say. But I will have to find out for the book...


Day #309

No comments:

Post a Comment