Skip to main content

JavaFX: Language investigations

Posted by opinali on May 25, 2010 at 3:57 PM PDT

My previous blog, presenting the Life program, was quite long, still not really complete. I've continued the work, but soon found some interesting surprises. This new blog starts investigating an API bug, then trips into some surprising language behavior, and ends in a proposal for a small change in the JavaFX Script language.

A Slider bug

I was annoyed to see that the Slider control ignores my attempts to enforce granularity - I wanted that only multiples of 50ms could be selected. Setting the recommended properties, like snapToTicks and majorTickUnit, only works for clicks ("paging"), but not for dragging the thumb. The user can still use the thumb to set the slider's value to any number in the min..max range. I looked up the JIRA and found that this is an already-known bug: RT-5914: No change in behavior after changing the snapToTicks flag for Slider. Fortunately there is a workaround:

def animSlider = Slider {
    override var value on replace { value = round(value/50) * 50 }
    snapToTicks: true majorTickUnit: 50 min: 0 max: 1000 value: 50 blockIncrement: 50 layoutInfo: LayoutInfo { width: 120 }
}

This demonstrates JavaFX Script's nice override var feature. The variable itself (class field) is not overriden, only the default value (including bind) and trigger. Overriding a trigger is similar to overriding a setter method in Java, except that you cannot remove existing triggers from the inherited class. This removes a common pitfall of OO (forgetting the super-call), which has some tradeoff in flexibility but it's a good tradeoff here because those on replace clauses are often critical to maintain the class state's consistency.

For much the same reasons, in my Java code all getters and setters are always final - no exceptions, no questions asked. When I have some rare scenario that justifies allowing subclasses to override the behavior of state changes, I create extra methods that don't follow the JavaBean get/set pattern. The final setters can be safely invoked from constructors, avoiding another pitfall from Java semantics, without breaking attribute encapsulation in constructors.

Trigger adventures

But... now I have another bug, although it's less severe and apparently not FX's fault. When I drag the thumb the on replace trigger rounds value like I wanted, but if you look at the Label that shows this value, it doesn't seem to be working at all - it shows the non-rounded value! Se the code:

Label { text: bind "({animSlider.value}) Generations: {world.gen} - Population: {world.pop}" }

I update Label.text with binding. When I drag the slider thumb, this actually does two things: 1) set Slider.value to the "raw" value, 2) invalidate this variable, causing any triggers and dependent bound expressions to fire.  I understand that I cannot rely on the relative order of execution of multiple dependent bindings; this is yet another reason why javafxc tries to enforce bound expressions to be functional-pure (although this can easily be circumvented). But I should be able to rely on the ordering of triggers before bindings. Something evil is going on!!

Suppose you write this code:

var x = 7 on replace { x = x * 2 }
var y = bind x + 1;

x = 77;
println("x={x}, y={y}");

What will be printed? As it turns out, "x=0, y=1". The on replace is recursive. It stops evaluating at -2147483648 * 2 => 0 (this is Integer maths), then 0 * 2 = 0. The field setter ignores attempts to assign the same value, so the recursion ends at 0 -> 0. (If x was a Double, it would stop at x = Infinity.) A on replace clause that doesn't reach a stable value, e.g. x = if (x == 1) 0 else 1, produces a StackOverflowError.

Back to the animSlider.value trigger, it sort-of-works because the clause value = round(value/50) * 50 doesn't risk creating a lot of recursive activations of the trigger. The first execution will round the value; the second execution will get an input already rounded to 50 units, so no extra recursion happens. But, the problem is that at least one recursion happens (unless of course I am really lucky and the initial value is already multiple of 50).

I have inspected briefly the code generated by javafxc for the field setters, including support for triggers and binding notifications. It's a bit confusing, e.g. with multiple "phases" of binding invalidation. It seems the language makes an effort to avoid problems like multiple firing of binding even if recursion happens due to triggers (or bound expressions with side effects). It's easy to see the resulting side effect in my Label bug, where the recursion plays a critical role to cause bindings to fire before the trigger.

Another fun puzzler: In the following code,

var x = 7.0 on replace { x = round(x) }
var y = bind x + 1;

x = 77.5;
println("x={x}, y={y}");

What gets printed?

Answer: "x=78.0, y=79.0". This is the expected output, which may be surprising because the on replace does one-time recursion for x = 77.5. But, here is the real puzzler:

var x = 7.0 on replace { x = round(x) }
var y = bind x + 1 on replace {};

x = 77.5;
println("x={x}, y={y}");

The only difference is that var y now contains a trigger too (even though it's an empty one). Sparing you from the surprise, the new code will print "x=78.0, y=78.5". This happens because binding is lazy by default in JavaFX 1.3+; but if the bound variable also has a trigger, it becomes eager. This changes the order of the various events (two executions of x's setter due to recursion; execution of x's trigger and y's bound expression). Only eager binding is subject to the whole mess caused by recursion. Lazy binding is safe because (I guess) no matter how many times the setters and triggers (and maybe eager bindings) are invoked, all the language does is marking variables assigned to dependent bound expressions as invalid; they will only be reevaluated when somebody reads their value, usually after the whole recursion deal is complete.

In the Label.text bug, the problem is that the Label control should be using a trigger internally, so it can update the presentation of its value when the text property changes.

Language growing pains

I proceeded to search the JIRA for this bug, and quickly found it in JFXC-4284: self-modified in on-replace: Wrong binded value is displayed. This bug was a regression caused by the many binding enhancements in JavaFX 1.3; fortunately, a fix is already available and scheduled to ship in 1.3.1.

The JIRA evaluation includes a comment "I have a fix, however, the order that the on-replace is called is undefined, and it is currently called last, this may even be forced by correct semantics (...) The state is being set to valid after on-replace. Correct behavior is that all state is set before on-replace is called." We're getting a fix soon; but still I'm not happy.

Defining that some behavior is "undefined" is necessary and good in some situations; remarkably in concurrency (e.g. which thread wins a lock when many are blocked waiting for it), or in algorithms that need some flexibility for reasons that contribute to their very purpose (e.g. the iteration order of a HashMap). But this is the exception, not the rule. In most cases, completely well-defined behavior is very important even in APIs, and much more in language features. Suggestion: The ordering of triggers and binding evaluation should be defined, at least in the extend that's possible and makes sense. (In particular, it's OK to define as unspecified the relative order of multiple bound expressions that must be reevaluated at some point.)

The javafxc team itself has filed several bugs complaining that the compiler sometimes produces different code for the same input, due to internal HashMaps that didn't preserve iteration order. This order is irrelevant for the correctness of the produced code; still, it's a problem for the toolchain - tools could work around it, but at the expense of extra complexity and effort. These bugs have been fixed, usually by throwing in a LinkedHashMap.

And just beating a dead horse, the order of trigger execution in 1.3 is not random; that would indeed be much better! The order is well-defined (although not documented), it's just illogical and in the worst possible way: 99% of the time it matches the programmer's intuition, so programmers get used to think that such observed order is reliable; but it can sometimes differ from that - and as a side effect of code artifacts that should not have this impact.

Looking also at the forest: For binding, the language is already designed to enforce side-effect-free expressions. This is the way to go, I hope that future releases of javafxc will perfect the pureness validation. (Possible if the compiler marks all functions that have side effects, or invokes others that have, including all APIs; so all that no "impure" code can be invoked from a bound expression.) This would have extra bonus of not allowing any new recursion caused by updates to variables with triggers, not to mention extra optimization opportunities.

But, what about triggers? This feature serves the dominant, imperative part of the language, so functional-purism is not an option. Perhaps we could only forbid self-modification? But the workaround suggested for the Slider bug is actually a great use of this facility: the "post-processing" of the value being set is a common purpose of triggers - maintain object invariants. This is a feature, not a bug. But it produces at least one level of recursion, and that was sufficient to create mysterious bugs with eager binding. (And the latter is another essential feature; even in the über-lazy Haskell language they often need "strictness annotations" to enforce eager execution.)

A Proposal

RFE: Change triggers so we can still update the subject variable of a trigger, but self-update will not cause recursion: the trigger just assigns the field value directly (or return the new value so the calling setter may do that assignment in the most convenient time). I fail to see the utility of recursive trigger execution. If one wants that behavior, just invoke a (locally-)recursive function, that wouldn't cause new activations of setters, triggers or bound expressions.

As a second rationale, the recursion caused by self-update is counter-intuitive. I was surprised when I discovered this behavior, perhaps because coming from Java, I relate triggers to polymorphic setters. But in a setter method you don't set the field with a recursive call; you may assign directly to the field (it's the only place where that is allowed, for OO best-practices), or you may super-call an inherited setter. But no sane Java developer would write something like: setX (double newX) { setX(round(newX)); }. Not even with an if (x != newX) clause. It's just asking for trouble.

I wonder if JavaFX Script's designers created triggers that way because they considered it simple and homogeneous, avoiding a special case - the trigger semantics has a single rule: "any assignment causes invocation of the trigger". Well, except the initialization! that's already one special case, unless you dismiss it as another category (construction, non-destructive assignment)... Anyway, simpler language rules don't always lead to a simple global behavior, or to intuitive behavior, not to mention useful behavior. The fix for bug JFXC-4284 is not sufficient in my opinion; this fix will avoid some bugs caused by relative order of triggers and binding, but it won't avoid other bugs that we can easily imagine, like my example { x = x * 2 } that reduces any value to zero or infinite.

My proposed change is not a silver bullet against all bugs of that kind. The trigger can invoke a function that will update the same variable containing that trigger, causing recursion again. Both triggers and bound expressions may invoke functions that will update unrelated variables, but those variables may have their own triggers resulting in indirect recursion, which is even more confusing and much more difficult for a compiler to prevent (although the improvement of javafxc's validation of pure bound expressions could at least isolate these risks to triggers alone). But we can avoid >90% of the bugs, for the most common usage. And there is no tradeoff that I can see (but correct me if I'm wrong).

Finally, it's tempting to imagine if we can go even further and make triggers bullet-proof, by having a smarter compiler that prevents any "dangerous" code in triggers. But this is an intractable problem; this last mile must be walked by best-practices. My new rule: Triggers and bound expressions should avoid any kind of I/O, any expensive computation, and any large-scale side effects (for bound expressions, no side effects at all). They should (at worst) only have "lightweight" side effects, such as setting a flag that will later cause some I/O or expensive computation to happen. Remember that both triggers and bound expressions (and any external functions they invoke) are executed in the middle of a complex mechanism that has unbounded execution. Even if the JavaFX teams accepts my suggestion of removing recursion from triggers with self-modification, it's not hard to write a program that, with too much work and side effects in triggers and bound expressions, will enter in a never-ending chain of invalidations.

UPDATE: Filed the bug JFXC-4382: Avoid trigger recursion for self-update. I've also found a few interesting correlations with other bugs - and that was a 15-minute research in the JIRA.

Comments

Cross recursion

Avoiding recursion on self-modification is a good idea, I don't find myself a useful usage for this recursion...
But it isn't enough, sometime we have cross-recursion, alas.
I hit that when searching a solution for the Resizing and positioning a stage with a background image thread: we have a trigger on scene's width and height and when changing one, we have to change the other to keep them proportional. I tried to avoid recursion with boolean flags but the code is far from perfect, not sure if that's my approach which is faulty or if I hit a limitation of JavaFX (a common question with such young language/framework).

Another (future) solution

JFXC-685: Trigger on f(x, y, z)...: This would allow you to write a single trigger that only fires when the aspect-ratio is broken. This RFE is not yet scheduled for a fix, but it's one of the issues that I identified that would probably be essier to do if my new suggestion (JFXC-4382) is implemented.

It's surely much easier to impl these relatively small RFEs, than something much bigger like (even simple) lightweight objects.

Interesting problem

The issue here is that, when the user drags the window corner, this trigger a single event that is processed internally by JavaFX and causes both Stage.width and Stage.height to be changed. There is no way to handle the aspect-ratio requirement with isolated triggers on these properties, that won't be either buggy, fragile, or a big ugly hack. In this case I suggest (if possible) to just intercept that dragging event, and do the aspect ratio adjustment before changing width/height.

It's also a good argument for demanding that the pair of attributes width/height is replaced by a single attribute "dimension: Dimension2D". That way you would have a single trigger on this attribute, and you could handle the aspect-ratio problem trivially. But I suppose that all those groups of attributes like x/y, width/height, translateX/translateY etc., are an optimization to avoid the allocation of a large number of objects like Point2D, Dimension2D or Rectangle2D. Until the JVM supports "lightweight objects", we must live with this kind of performance tradeoffs... I wonder if JavaFX itself could introduce some simple support for lwobjs, e.g. a Point2D "struct" could be compiled down to a pair of float variables; represented in classes a pair of fields, passed in functions as a pair of parameters etc. - only boxed/unboxed into a real Point2D object when strictly necessary e.g. in a downcast to Object.