Tuesday, February 28, 2012

Overloading Dart Operators for Great Evil

‹prev | My Chain | next›

One of the cool things about working through a brand new language like Dart is happening across weird little nooks and crannies of the language specification. One such example is the call operator which seems to be a way to get objects to behave like functions. Or I could be misreading the spec, I have a hard time with that kind of thing.

My best guess is that I can define an operator named call() to set this up, so I will try this out with my Hipster MVC framework. For now, I will make it an alias for save() on the HipsterModel class.

I have already defined other operator methods in Dart. In the HipsterModel, for instance, I have the square brackets operator that allows developers to lookup attributes in models as if the model were a Map (e.g. comic_book['title']). This is defined using the operator keyword:
class HipsterModel implements Hashable {
  // ...
  operator [](attr) => attributes[attr];
}
So I try doing something similar with call():
class HipsterModel implements Hashable {
  // ...
  operator call([callback]) {
    save(callback: callback);
  }
}
But, when I try to load the application that is build with Hipster MVC, I get a compile time error:
Internal error: 'http://localhost:3000/scripts/HipsterModel.dart': Error: line 23 pos 12: invalid operator overloading
  operator call([callback]) {
           ^
After fiddling a bit, I concede that perhaps I mis-read the spec. Perhaps call is just meant to represent the parens operator:
class HipsterModel implements Hashable {
  // ...
  operator ([callback]) {
    save(callback: callback);
  }
}
With that, I no longer receive a compile time warning. When I try to create a new model with something like:
new_model(callback:(event) {
  print("added a new model!");
});
I am greeted with a run time error:
2FIXME:1Exception: Object is not closure
Stack Trace:  0. Function: 'HipsterCollection.create' url: 'http://localhost:3000/scripts/HipsterCollection.dart' line:48 col:14
 1. Function: 'AddComicForm._submit_create_form@3b72b637' url: 'http://localhost:3000/scripts/Views.AddComicForm.dart' line:89 col:22
 2. Function: 'AddComicForm.function' url: 'http://localhost:3000/scripts/Views.AddComicForm.dart' line:24 col:26
 3. Function: 'HipsterView.function' url: 'http://localhost:3000/scripts/HipsterView.dart' line:41 col:15
 4. Function: 'EventListenerListImplementation.function' url: 'dart:htmlimpl' line:23128 col:35
Despite receiving my very favorite exception warning ("Object is not closure" seems to be little more than "something went wrong"), this is progress.

Maybe now is when I need call():
new_model.call(callback:(event) {
  print("added a new model!");
});
This changes the error, but I am still not quite right:
Exception: NoSuchMethodException : method not found: 'call'
Receiver: Instance of 'ComicBook'
Arguments: [Closure]
Stack Trace:  0. Function: 'Object.noSuchMethod' url: 'bootstrap' line:669 col:3
 1. Function: 'HipsterCollection.create' url: 'http://localhost:3000/scripts/HipsterCollection.dart' line:48 col:19
 2. Function: 'AddComicForm._submit_create_form@3b72b637' url: 'http://localhost:3000/scripts/Views.AddComicForm.dart' line:89 col:22
 3. Function: 'AddComicForm.function' url: 'http://localhost:3000/scripts/Views.AddComicForm.dart' line:24 col:26
 4. Function: 'HipsterView.function' url: 'http://localhost:3000/scripts/HipsterView.dart' line:41 col:15
 5. Function: 'EventListenerListImplementation.function' url: 'dart:htmlimpl' line:23128 col:35
I try:
new_model.(callback:(event) {
  print("added a new model!");
});
But get:
Internal error: 'http://localhost:3000/scripts/HipsterCollection.dart': Error: line 48 pos 15: identifier expected
    new_model.(callback:(event) {
              ^
Sigh. Re-reading the spec again, it really seems as though it ought to be call(). Perhaps it is not yet supported? I try a dummy operator to see if the message is the same, and indeed, "invalid operator overloading" seems to mean that the token is not an operator:
Internal error: 'http://localhost:3000/scripts/HipsterModel.dart': Error: line 23 pos 12: invalid operator overloading
  operator asdf([callback]) {
           ^
I try the latest version of Dartium only to find the same thing. Grr...

It is at this point that I finally stumble across a description of how call() will work in Dart. At the very top, it states that this is brand new and not implemented yet. Darn it. On the bright-side, it does seem that my understanding of how it will work is correct.

Regardless, I refuse to give up until I have done something tonight, no matter how ridiculous. So I overload the pipe operator for HispterModel so that it saves itself and pipes the response into the supplied callback:
class HipsterModel implements Hashable {
  // ...
  operator |(callback) => save(callback: callback);
}
I can then invoke this save/pipe like so:
new_model | (event) {
  print("added a new model!");
};
And that works.

It seems only fitting to end this post thusly: Mwah haha!


Day #310

2 comments:

  1. I think you have misunderstood the purpose of operators. They are not supposed to make objects behave like functions but rather (traditionally, you can always use them for other purposes if you want so) define the interaction with other data types.

    A good example of the use, in my opinion, is vectors. You usually need to add 2 vectors or scale one. This could be done using functions like this:

    v1.add(v2);
    v1.scale(k);

    or you could define operators *(int) and +(Vector) and write like this:

    v1 = v1 * v2;
    v1 = v1 * k;

    This clarifies and simplifies using your objects, doesn't it? It's a great thing, especially when writing some sort of math library.

    ReplyDelete
    Replies
    1. Yah, some of this was very contrived. I intended this more as a "what's possible" post than a "best practices" post. So, your point it well taken -- thanks!

      I would only point out that the purpose of the `call` operator is to make objects behave like a function, but that's a weird, special case.

      Delete