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
Love the term "tear off".
ReplyDeleteI wonder if these tear-offs qualify as thunks? (https://en.wikipedia.org/wiki/Thunk)
Love the term "tear off".
ReplyDeleteI wonder if these tear-offs qualify as thunks? (https://en.wikipedia.org/wiki/Thunk)
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.
DeleteI would think they'd qualify as thunks, but there is almost certainly an obscure computer sciency reason that they don't :P
google 2225
ReplyDeletegoogle 2226
google 2227
google 2228
google 2229