Saturday, August 3, 2013

Simple Iterable Classes in Dart


While updating Dart for Hipsters I have been struggling mightily with Collections. Specifically, the collection part of the MVC example library that is used throughout the book is giving me all kinds of problems.

Back in the day, Dart used to have a Collection interface that was implemented by Lists and Maps. In the book, I have a comic book collection that I use to illustrate the Collection portion of MVC. It used to implement the old Collection interface:
class ComicsCollection implements Collection {
  // ...
}
Since it implemented Collection, it had to define a bunch of methods that are part of the Collection interface:
class ComicsCollection implements Collection {
  // ...
  void forEach(fn) => models.forEach(fn);
  int get length => models.length;
  operator [](id) { /* ... */ }
  iterator() => models.iterator();
  bool get isEmpty => models.isEmpty;
  map(fn) => models.map(fn);
  filter(fn) => models.filter(fn);
  contains(element) => models.contains(element);
  reduce(initialValue, fn) => models.reduce(initialValue, fn);
  every(fn) => models.every(fn);
  some(fn) => models.some(fn);
}
It was a pain to implement all of those methods, but there was not much choice. By declaring that my ComicsCollection class implements the Collection interface, I am stating my intention to define those methods.

If anything, the situation has gotten worse. The Collection interface has gone away, replaced (more or less) by Iterable. If anything, Iterable requires me to define even more methods. If my ComicsCollection class subclasses Iterable with none of the necessary methods:
class ComicsCollection extends Iterable {
  // ...
}
Then dartanalyzer complains quite a bit:
[warning] Missing inherited members: 'Iterable.isNotEmpty', 'Iterable.isEmpty', 'Iterable.any', 'Iterable.skip' and 19 more (/home/chris/repos/csdart/Book/code/mvc/public/scripts/comics.dart, line 33, col 7)
Before today, I had been hand-coding each of those 23 methods:
class ComicsCollection extends Iterable {
  // ...
  void forEach(fn) => models.forEach(fn);
  int get length => models.length;
  operator [](id) { /* ... */ }
  get iterator => models.iterator;
  bool get isEmpty => models.isEmpty;
  bool get isNotEmpty => models.isNotEmpty;
  map(fn) => models.map(fn);
  contains(element) => models.contains(element);
  reduce(fn) => models.reduce(fn);
  every(fn) => models.every(fn);
  any(fn) => models.any(fn);
  skip(n) => models.skip(n);
  toSet() => models.toSet();
  get last => models.last;
  get single => models.single;
  singleWhere(fn) => models.singleWhere(fn);
  fold(initial, fn) => models.fold(initial, fn);
  take(n) => models.take(n);
  takeWhile(fn) => models.takeWhile(fn);
  join([sep=""]) => models.join(sep);
  where(fn) => models.where(fn);
  elementAt(n) => models.elementAt(n);
  lastWhere(fn, {orElse()}) => models.lastWhere(fn, orElse: orElse);
  toList({growable: true}) => models;
  skipWhile(fn) => models.skipWhile(fn);
  expand(fn) => models.expand(fn);
  firstWhere(fn, {orElse()}) => models.firstWhere(fn, orElse: orElse);
  get first => models.first;
}
But that is crazy! Are Dart programmers really supposed to do that whenever they want to make a thing that behaves like a list or a map?

In Ruby, anything can be an Enumerable object as long as it mixes in the Enumerable module and defines an each() method. Mercifully, I stumbled across the same feature in Dart today. In the dart:collection library, there are a number of “Base” classes, including the IterableBase class. It turns out that this class serves the same purpose as Enumerable in the Ruby world. As long as my class defines an iterator method, then it has all of the methods that an iterator has.

So to obtain all of this, I import the dart:collection library, define ComicsCollection as a subclass of IterableBase, and define the iterable getter:
import 'dart:collection';
class ComicsCollection extends IterableBase {
  // ...
  List models;
  get iterator => models.iterator;
}
With that, I get all 23 of those methods above for free. And dartanalyzer is perfectly happy with me. I suspected that a language that give us hash rocket return functions and instance variable assignment in constructor declarations must offer some kind of shortcut for iterators. I love that it only requires 1 method definition—just like Ruby.

Looking through the dart:collection library a bit more, it seems that there are at least two other options for light weight iterable definitions. First, I could mixin the IterableMixin. I may have to think of a good use-case for that just to play with it. Another option would be to make my ComicsCollection a subclass of ListBase instead of IterableBase.

Since Dart lists are iterable, if I subclass ListBase, I would get all of the Iterable methods plus list-like methods (reverse, indexAt, etc). Instead of defining an iterator getter as I did when I subclassed IterableBase, I need to define four methods in the ListBase subclass:
class ComicsCollection extends ListBase {
  // ...
  List models;
  int get length => models.length;
  void set length(int i) { models.length = i; }
  operator [](int i) => models[i];
  operator []=(int i, v) => models[i] = v;
}
The length getter and setter and the square bracket operator methods are enough to get iterable methods and list methods. Since the ComicsCollection is a wrapper for that models list, it probably makes sense to do something like this.

Regardless of how I end up implementing ComicsCollection, I am thrilled to be able to do so by defining 1 or 4 methods instead of 23. Thrilled!


Day #832

No comments:

Post a Comment