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