Saturday, February 11, 2012

Inheritance Grab Bag in Dart

‹prev | My Chain | next›

I am a bit annoyed that there does not seem to be a way to pass a class name to a function in Dart. Even so, it is not a huge hardship. Instead of telling my comic book collection's super class the model class to use when creating records, I have to supply a function that creates the models:
class Comics extends HipsterCollection {
  Comics() {
    url = '/comics';
    model = (attrs) => new ComicBook(attrs);

    on = new CollectionEvents();
    models = [];
  }
}
Like I said, not a big. I would prefer to be able to treat the class name as an object that can be passed:
model = ComicBook;
Which the super class could use to construct object as:
class HipsterCollection {
  // ...
  modelMake(attrs) {
    new model(attrs);
  } 
  // ...
}
But neither passing a reference to a class works nor does trying to treat a variable as a class. At least not in Dart.

No matter, perhaps I can do other things to clean up my sub-class definition. First of all, the definition of the on and models properties surely does not need to be done in every sub-class of HipsterCollection. The first thing to try is defining a constructor in my super class:
class HipsterCollection<M extends HipsterModel> {
  var url, on, model;
  List<M> models;

  HipsterCollection() {
    print("[HipsterCollection]");
    on = new CollectionEvents();
    models = <M>[];
  }
  // ...
}
(I am retaining the generics type information from yesterday, if for no other reason than documentation).

And that works. Not only does my collection still populate, but my super-class-only print("[HipsterCollection]"); statement displays in the console output:


I was unsure that this would work. I more-or-less expected that, since my Comics sub-class defined its own constructor, that the super class constructor would not be evaluated. I do not see anything in the constructor section of the language spec about the subject, so I try out a simple case like:
class A {
  A() { print("A"); }
}

class B extends A {
  B() { print("B"); }
}

class C extends B {
  C() { print("C"); }
  C.special(c) { print(c); }
}

main() {
  var c = new C();
  print("==");
  var c1 = new C.special("whee!");
}
(try.dartlang.org)

In both cases, the super-class contructors in C's ancestor chain are invoked, so this seems at least consistent behavior:
A
B
C
==
A
B
whee!
With the on and models property definitions safely back in the HipsterCollection base class constructor, my Comics collection is looking down right sparse:
class Comics extends HipsterCollection {
  Comics() {
    url = '/comics';
    model = (attrs) => new ComicBook(attrs);
  }
}
Hrm... since I cannot assign a class to the model attribute like I can in Backbone.js, there is no need to define that model function constructor as a property or to do so inside the constructor. It makes more sense to define that as a static class method:
class Comics extends HipsterCollection {
  Comics() {
    url = '/comics';
  }

  static modelMaker(attrs) {
    return new ComicBook(attrs);
  }
}
Only that does not seem to work. Back in the base class, I try:
class HipsterCollection<M extends HipsterModel> {
  // ...
  static modelMaker(attrs) {
    print("[super.modelMaker]");
    return null;
  }
  // ...
  _handleOnLoad(event) {
    var request = event.target
      , list = JSON.parse(request.responseText);

    list.forEach((attrs) {
      models.add(modelMaker(attrs));
    });

    on.load.dispatch(new CollectionEvent('load', this));
  }
}
But the only messages that I see are from the static method in the super class. Reading through the spec, I see this is intentional:
Inheritance of static methods has little utility in Dart. Static methods cannot be overridden... Experience shows that developers are confused by the idea of inherited methods that are not instance methods.
Sigh. It sure is hard for a fella to communicate a constructor class from sub-class to super-class in Dart. In the end, I settle for an instance method:
class Comics extends HipsterCollection {
  Comics() {
    url = '/comics';
  }

  modelMaker(attrs) => new ComicBook(attrs);
}
Back in the HipsterCollection base class, I then declare this method as abstract:
class HipsterCollection<M extends HipsterModel> {
  // ...
  abstract M modelMaker(attrs);
  // ...
  _handleOnLoad(event) {
    var request = event.target
      , list = JSON.parse(request.responseText);

    list.forEach((attrs) {
      models.add(modelMaker(attrs));
    });

    on.load.dispatch(new CollectionEvent('load', this));
  }
}
I must admit it nice to have compile-time, or at least formal, mechanism to catch any method-not-defined-in-sub-class mistakes that I might make.

One last thing that could make the concrete class definition even smaller would be to define the url property in my sub-class definition rather than in the constructor:
class HipsterCollection<M extends HipsterModel> {
  var url, on;
  // ...
}

class Comics extends HipsterCollection {
  var url = '/comics';
  modelMaker(attrs) => new ComicBook(attrs);
}
Unfortunately, that does not work as I receive the following error:
Internal error: 'http://localhost:3000/scripts/Collections.Comics.dart': Error: line 7 pos 7: field 'url' of class 'Comics' conflicts with instance member 'url' of super class 'HipsterCollection'.

  var url = '/comics';
      ^
Hunh? Why is that a "field"? I really intended it to be the instance member. Trying this out in the most simple case seems to actually work:
class A {
  var foo;
}

class B extends A {
  var foo = 'bar';
}

main() {
  var b = new B();
  print(b.foo);
}
(try.dartlang.org)

That works just fine, printing out the value of "foo" from the sub-class. Reading through the spec proves of little help. It seems to use field and instance variable more-or-less interchangeably. It does distinguish in places between field and getter, but there is a difference between instance variable and getter as well. There is no discussion in the spec on redefining an instance variable in a sub-class, though the super-class's instance variables are definitely available to the sub-class:
The instance variables of a class C are those instance variables declared by C and the instance variables inherited by C from its superclass,
Since Dartium and try.dartlang.org are unable to agree, and since redefining does not work in Dartium, I am forced to stick with assigning the instance variable in the constructor. Unless...

I do not expect this work, but what if I define a getter in the sub-class?
class Comics extends HipsterCollection {
  get url() => '/comics';
  modelMaker(attrs) => new ComicBook(attrs);
}
I am somewhat surprised, but that does work. It even works in the simple try.dartlang.org case.

I suppose that I can convince myself that this ought to work. In Dart, instance variables have implicit getters/setters. Therefore, I ought to be able to override those getters and setters. I thought I came across something in the spec that prohibited this, but it was probably just something like: "It is a compile-time error if a class has both a getter and a method with the same name". That does not apply in this case because I am overriding the implicit getter with an explicit getter.

This is a nice, compact format, but the problem, of course is that my url= setter would update the value of the url instance variable but have no effect on the url getter which is now hard-coded. In this case, this is not a big deal because the URL in a client-side MVC framework rarely needs to change. So, back in the base class, I again have an abstract method, this time a getter:
class HipsterCollection<M extends HipsterModel> {
  var on;
  List<M> models;

  abstract M modelMaker(attrs);
  abstract String get url();
  // ...
}

class Comics extends HipsterCollection {
  get url() => '/comics';
  modelMaker(attrs) => new ComicBook(attrs);
}
That will work just fine.

In the case where I need an implicit getter and setter, I will have to stick with assigning the instance variable in the constructor. Or hope that the noted behavior is a bug that will eventually be addressed.

That was quite the tour of the world on inheritance in Dart. At this point, I think I have a good handle on it. Well, maybe not that good, but hopefully enough to justify moving on to another topic tomorrow.


Day #293

No comments:

Post a Comment