Monday, January 4, 2016

The Coolest Command is Also the Dartiest


Something strange happened yesterday. I wrote some Dart code that just worked™.

OK, that's not too strange—Dart is designed to be familiar and easy to understand, after all. What's strange is that I did something that would not have worked in nearly any other language—passing a reference to the method of an object instance. I cannot recall if I knew this would work or if I wasn't thinking and just got lucky. Probably the latter, but either way, it worked.

This definitely will not work in JavaScript, for example. If I have a Robot class in JavaScript:
Robot = function(){
  this.x=0; 
  this.y=0;
}
Robot.prototype.getXyLocation = function() { 
  return "" + this.x + ", " + this.y; 
}
Robot.prototype.moveForward = function() { 
  this.y++;
}
Then I can create an instance of Robot and move it forward by calling the moveForward() method:
var r = new Robot();
r.moveForward();
r.getXyLocation();
// => "0, 1"
But I cannot grab a reference to the moveForward() method from r, call that reference directly, and expect it to update r:
var c = r.moveForward;
c();
r.getXyLocation();
// => "0, 1"
The r remains at 0, 1 because of this, of course. Undesirable or unexpected behavior in JavaScript always comes back to this.

In this case, c is a function—the same one defined on the Robot prototype. Invoking it did increment this.y just as it did in the Robot method, but the this inside c refers to the top-level client object. I could apply() or call() the c function with the appropriate this context:
c.call(r);
r.getXyLocation();
// => "0, 2"
By explicitly setting the method context with call() I am able to update r. But that's a pain, like just about everything dealing with this in JavaScript. If I wanted to pass that method in code, I would always have to pass it along with the object. In other words, I would need some explicit form of the command pattern.

But in Dart, that turns out to be unnecessary. I can define a similar Robot class:
class Robot {
  int x=0, y=0;
  String get xyLocation => "$x, $y";
  void moveForward() { y++; }
}
Then I can move the robot forward, tear off the method into a variable and move the robot forward using that variable method:
main () {
  var robot = new Robot();
  robot.moveForward();
  
  var c = robot.moveForward;
  c();
  
  print("The robot is now at: ${robot.xyLocation}.");
}
After that, the robot has moved forward twice:
The robot is now at: 0, 2.
It turns out that I should have remembered this. There was a minor brouhaha over this on the Dart list over the summer. These tear-off methods are closures built into the language. This is a pretty cool feature, so the kerfuffle was not over this, rather over a more generalized tear-off syntax.

If I wanted to tearoff a getter like xyLocation, for instance, I could not assign robot.xyLocation—that would assign the value of the getter. To obtain a reference to getter method, I would have to use robot#xyLocation:
main () {
  var robot = new Robot();
  var loc = robot#xyLocation;
  var c = robot.moveForward;

  // Regular method invocation...
  robot.moveForward();

  // Tear-off method invocations...
  c();
  print("The robot is now at: ${loc()}.");
}
It is a little strange to tearoff a getter and have to invoke it with parenthesis, but the main source of consternation was the hash symbol to obtain the tearoff. I would tend to agree with those that object to that particular syntax—it is uglier than the regular syntax and it it easy to confuse with Dart symbols which start with the hash symbol as well. Still, it is nice to know that it is possible should I ever need it.

More importantly, I think I have identified the Dartiest possible command pattern. I can't believe that I almost overlooked it!

Play with this on DartPad (the generalized/hash tearoff syntax is not supported): https://dartpad.dartlang.org/f3a717a9743953733de8.


Day #54

4 comments:

  1. Love the term "tear off".

    I wonder if these tear-offs qualify as thunks? (https://en.wikipedia.org/wiki/Thunk)

    ReplyDelete
  2. Love the term "tear off".

    I wonder if these tear-offs qualify as thunks? (https://en.wikipedia.org/wiki/Thunk)

    ReplyDelete
    Replies
    1. Yeah, it's a great name. I had never heard of them before Dart. They were introduced very early on and presented such that the name seemed to be fairly well known.

      I would think they'd qualify as thunks, but there is almost certainly an obscure computer sciency reason that they don't :P

      Delete