Tuesday, November 5, 2013

Weird Static Functions in Dart


The biggest mistake that I made in Dart for Hipsters was including an evolving application narrative. I really like the idea, and think it works to illustrate much of the power of Dart. But, wow, it is a pain when something fundamental changes that I use early on in the book. After this newest edition is out the door, I do not expect that to happen again (because the language is much more stable), so maybe this is just a documentation technique to be avoided when a language is pre-alpha.

The change of events from a traditional list of callbacks to a stream of events proved quite the challenge. I think I have it more or less under control at this point. Interestingly enough, much of the refactoring that I have done for the book has not trickled down into Hipster MVC. This package is the end result of much of the book's narrative: writing an application from scratch and leveraging Dart to extract out a library.

The book currently uses pure streams in this library, but Hipster MVC is still weighted down with some event code. And a recent update to core Dart broke the Hipster MVC build. Rather than limp along with the old event interface, it is time to switch to streams.

This mostly involves deleting old code that implemented an event-like interface:
abstract class HipsterCollection extends IterableBase {
  CollectionEvents on = new CollectionEvents();
  // ...
}
The bulk of the deleted code was in the CollectionEvents class and super classes. In their place I need streams and stream controllers. I start with the controllers:
abstract class HipsterCollection extends IterableBase {
  StreamController _onLoad, _onAdd;
  // ...
  HipsterCollection(){
    _onLoad = new StreamController.broadcast();
    _onAdd = new StreamController.broadcast();
  }
  // ...
}
As the name suggests, stream controllers are way to control streams. Specifically, they expose an add() method that lets me add objects to streams whenever something happens. When a model is added to the collection, I can add it to the _onAdd controller so that subscribers to the stream can do something in response:
abstract class HipsterCollection extends IterableBase {
  // ...
  add(model) {
    models.add(model);
    _onAdd.add(model);
  }
}
It does no good to add objects to streams without a subscribe-able stream, so I finish with two getters—one for each of the streams that HipsterCollection currently exposes:
abstract class HipsterCollection extends IterableBase {
  StreamController _onLoad, _onAdd;
  // ...
  HipsterCollection(){
    _onLoad = new StreamController.broadcast();
    _onAdd = new StreamController.broadcast();
  }
  Stream get onLoad => _onLoad.stream;
  Stream get onAdd => _onAdd.stream;
  // ...
  add(model) {
    models.add(model);
    _onAdd.add(model);
  }
}
With that, application code can listen for load events with a simple collection_obj.onLoad.listen((_){ /* ... */ }).

All of that goes well. I delete a bunch of code and tests, which is always nice. But I run into a problem that I do not recognize from dartanalyzer:
Analyzing lib/hipster_collection.dart...
[warning] Concrete classes that implement Function must implement the method call() (/home/chris/repos/hipster-mvc/lib/hipster_sync.dart, line 9, col 7)
1 warning found.
This is coming not from the HipsterCollection class, but from the HipsterSync class that chooses the method for persistence at runtime. It does so via a simple conditional in the call() static method:
class HipsterSync {
  // ...
  // static method for HipsterModel and HipsterCollection to invoke -- will
  // forward the call to the appropriate behavior (injected or default)
  static Future<dynamic> call(method, model) {
    if (_injected_sync == null) {
      return _defaultSync(method, model);
    }
    else {
      return _injected_sync(method, model);
    }
  }
  // ...
}
What is odd here is that the warning is complaining that concrete classes (which this is) must implement the method call() (which this does). Granted, this is a static call() method, but I am not even declaring this as implementing Function, so what gives? There is only one way to answer that question... to the language spec!

In the spec, I find that:
If a type I includes an instance method named call(), and the type of call() is the function type F, then I is considered to be a subtype of F.
I am not convinced that applies in this case, but I am probably asking for trouble using call(), which is not quite a keyword, but kind of is.

For now, I think that I will ignore this error. I rather like HipsterSync.call() as an API call. Then again, it is mostly a library internal, so I may very well end up deciding that discretion is the better part of valor on this one.


Day #926


No comments:

Post a Comment