Monday, August 4, 2014

The Polymer.dart ComputedProperty Annotation


The new annotations in Polymer.dart are… interesting.

As best I can tell, the purpose behind them is mostly to ensure that the values that they annotate are available regardless of when they are accessed. To better understand that, I need to explore timing accessors. But first, I would like to complete the new annotation tour with a look at @ComputedProperty.

The old way of creating a setting property, for example the number of toppings on a particular side of the <x-pizza> element, might look something like:
@CustomTag('x-pizza-toppings')
class XPizzaToppings extends PolymerElement {
  // ...
  int get numToppings => model.length;
  // ...
}
That is a nice, straight-forward getter method. How could @ComputedValue possibly improve on that?

To answer that question, I renamed the “old” way of setting a computed property as numToppings_old and re-implement numToppings with @ComputedProperty:
@CustomTag('x-pizza-toppings')
class XPizzaToppings extends PolymerElement {
  // ...
  @ComputedProperty("model.length")
  int get numToppings => readValue(#numToppings);

  int get numToppings_old => model.length;
  // ...
}
I have to admit that there is some pretty cool meta programming going on in there. The @ComputedProperty annotation declares that the next getter is obtained via the Polymer expresssion of model.length. Then, the readValue() method works in conjunction with this annotation to compute the property value. It seems a little strange to repeat numToppings like that—once to declare the name of the computed property and once as a symbol argument to readValue(). My guess is that this is a limitation of annotations—that even though @ComputedProperty is able to associate its argument with the getter immediately below it, readValue() is not able to do the same.

The end result is seemingly unnecessary indirection and the slight DRY violation. So is it worth it?

To answer, I update the containing <x-pizza> element to print both the new and old ways of computing the property:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  XPizzaToppings get firstHalf => $['firstHalfToppings'];
  XPizzaToppings get secondHalf => $['secondHalfToppings'];
  XPizzaToppings get whole => $['wholeToppings'];

  updatePizzaState([_]) {
    print("First half: ${firstHalf.numToppings} (${firstHalf.numToppings_old})");
    print("Second half: ${secondHalf.numToppings} (${secondHalf.numToppings_old})");
    print("Whole: ${whole.numToppings} (${whole.numToppings_old})");
  }
  // ...
}
The result when loading <x-pizza>? Both values are identical:
First half: 0 (0)
Second half: 0 (0)
Whole: 0 (0) 
And, when I update the toppings… Both computed properties are identical for all toppings:
First half: 2 (2)
Second half: 1 (1)
Whole: 2 (2)
So I am a little at loss to explain why I might want to use the @ComputedValue instead of the more concise, simple getter format that I have been using all along.

My understanding for the inspiration behind the new annotation is that, without them, timing issues can arise. I probably need to come up with a better way to produce such issues, but my first inclination is to try to access the properties when the parent <x-pizza> element is first created:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  XPizza.created(): super.created() {
    print("[ctor] First half: ${firstHalf.numToppings} (${firstHalf.numToppings_old})");
    print("[ctor] Second half: ${secondHalf.numToppings} (${secondHalf.numToppings_old})");
    print("[ctor] Whole: ${whole.numToppings} (${whole.numToppings_old})");
  }
  // ...
}
But even that produces valid results:
[ctor] First half: 0 (0)
[ctor] Second half: 0 (0)
[ctor] Whole: 0 (0)
There must be a use-case in which these annotations come in handy, but this is clearly not one of them.

I have a decent handle on how to use the new annotations in Polymer.dart at this point. Tomorrow I will dig through some unit tests to see if I can better understand when to use them.


Day #143

No comments:

Post a Comment