Thursday, January 12, 2012

Dart (Pseudo) Mixins

‹prev | My Chain | next›

I am perfectly up front for harboring and intense, but completely rational dislike for inheritance. It is one of those things that works quite well in academics but is rarely useful in real life. But, because most of us learned that object oriented programming is synonymous with inheritance, inheritance often gets used when it does not make sense. And when it does not make sense to use a pattern, bad things are sure to follow.

Dart supports inheritance. I am sorely tempted to completely omit this tidbit of information from Dart for Hipsters. I will probably wimp out and mention it in passing, but what I will focus on (if it works) are mixins.

Mixins are a mechanism for adding a slice of functionality. Consider two shape classes:
#library('Super amazing shapes!');

class Rectangle {
  final num height, width;
  Rectangle(this.width, this.height);
  area() => this.width * this.height;
  perimeter() => 2*this.width + 2*this.height;
}

class Circle {
  final num radius;
  Circle(this.radius);
  area() => 2 * Math.PI * this.radius * this.radius;
  perimeter() => 2 * Math.PI * this.radius;
}
To use them I might do something like:
#import('shapes01.dart');

main() {
  var r = new Rectangle(2, 10);
  print("The area of the rectangle is: " + r.area());
  print("The perimeter of the rectangle is: " + r.perimeter());
  print("");

  var c = new Circle(5);
  print("The area of the circle is: " + c.area());
  print("The perimeter of the circle is: " + c.perimeter());
  print("");
}
Which would result in the following output:
➜  mixins git:(master) ✗ dart main01.dart
The area of the rectangle is: 20
The perimeter of the rectangle is: 24

The area of the circle is: 157.079633
The perimeter of the circle is: 31.415927
Easy-peasey, but there is duplication in there that I might like to factor out into a series of functions:
p_area(shape) {
  print("Area: " + shape.area());
}

p_perimiter() {
  print("Perimeter: " + this.perimeter());
}
Dart defines the #source() directive to pull in arbitrary code. Might I be able to do something like:
class Circle {
  final num radius;
  Circle(this.radius);
  area() => 2 * Math.PI * this.radius * this.radius;
  perimeter() => 2 * Math.PI * this.radius;
  #source('pretty_shapes.dart');
}
Sadly no:
➜  mixins git:(master) ✗ dart main02.dart
'/home/cstrom/repos/dart-site/examples/command_line/mixins/main02.dart': Error: line 1 pos 1: library handler failed: '/home/cstrom/repos/dart-site/examples/command_line/mixins/shapes02.dart': Error: line 28 pos 3: identifier expected
  #source('pretty_shapes.dart');
  ^

#import('shapes02.dart');
^
The closest that I can come is to source the libary in main, and then inject the functions into the constructor:
#import('shapes02.dart');
#source('pretty_shapes.dart');

main() {
  var r = new Rectangle(2, 10, mixins:{
    'p_area':p_area, 'p_perimeter':p_perimeter'
  });
  r.p_area();
  print("");

  var c = new Circle(5);
  print("The area of the circle is: " + c.area());
  print("The perimeter of the circle is: " + c.perimeter());
  print("");
}
The Rectangle class then needs to support the optional mixins parameter. The noSuchMethod() method can then invoke the called method on the injected "mixin":
#library('Super amazing shapes!');

class Rectangle {
  final num height, width;
  var _mixins;

  Rectangle(this.width, this.height, [mixins]) {
    _mixins = mixins;
  }
  area() => this.width * this.height;
  perimeter() => 2*this.width + 2*this.height;

  noSuchMethod(String name, List args) {
    _mixins['p_area'](this);
  }
}
The pretty_shapes library also needs to change such that the functions accept a shape argument, on which the corresponding methods are called (instead of calling them on this):
p_area(shape) {
  print("Area: " + shape.area());
}

p_perimeter(shape) {
  print("Perimeter: " + shape.perimeter());
}
The result of running the script is now:
➜  mixins git:(master) ✗ dart main02.dart
Area: 20
Perimeter: 24

The area of the circle is: 157.079633
The perimeter of the circle is: 31.415927
Of course, now I have traded one for of duplication for another. I might try to solve this with inheritance from a "Mixinable" class:
#library('Super amazing shapes!');

class Mixinable {
  var _mixins;

  noSuchMethod(String name, List args) {
    if (_mixins != null && _mixins[name] != null) {
      _mixins[name](this);
    }
    else {
      throw new NoSuchMethodException(this, name, args);
    }
  }
}

class Rectangle extends Mixinable {
  final num height, width;

  Rectangle(this.width, this.height, [mixins]) {
    _mixins = mixins;
  }
  area() => this.width * this.height;
  perimeter() => 2*this.width + 2*this.height;
}

class Circle extends Mixinable {
  final num radius;

  Circle(this.radius, [mixins]) {
    _mixins = mixins;
  }
  area() => 2 * Math.PI * this.radius * this.radius;
  perimeter() => 2 * Math.PI * this.radius;
}
That will work, but... Since Dart only supports single inheritance, I cannot sub-class a "Shape" class if I decided it was necessary. Also, injecting methods seems fraught with danger. It really feels as though I am off the Dart rails here.

Although not ideal, it is better than nothing. Still, I think I will hold out hope for reflection to solve this for real—or at least a little better.


Day #263

1 comment:

  1. Mark Bennett pointed out: https://code.google.com/p/dart/issues/detail?id=33

    ReplyDelete