JEP Proposal: Fluent Bindings for multiple observables
Nir Lisker
nlisker at gmail.com
Tue Oct 28 20:30:21 UTC 2025
>
> You're right, I forgot about the existence of the Bindings class, and the
> helpers it has added for these cases. I barely use it since the addition
> of fluent bindings. That version is a lot more compact and a bit more
> workable.
The primary reason for that is that ReactFX had to introduce new property
> classes which are limited in how well they can interop with existing JavaFX
> code. With the fluent bindings additions the need for ReactFX is limited.
> That is not to say ReactFX doesn't offer anything interesting anymore :)
I don't use Bindings a lot since fluent bindings and have basically dropped
ReactFX since then too. ReacrFX goes very far and has a lot of options,
most of them cover rare cases. It's still very good to have a ready
implementation for all that.
Static imports are not really a plus for any argument in my view :)
> Compare AssertJ and Hamcrest to see what I mean.
I very rarely use them outside of test code, but when a class does
extensive work in the context of a service class (like Bindings and
Collectors) I could see using them. Also, I use Google Truth (nothing
against AssertJ) :) Interestingly, JavaFX has a dependency on Hamcrest.
It is an extension on the fluent API, it doesn't introduce anything that
> isn't possible currently, just like `map` and `flatMap` didn't introduce
> anything new, nor did `subscribe`. It is intended to be a more
> discoverable, fluent and modern API, and to fill a gap where one has to go
> from the fluent binding model to a static helper class model as soon as you
> go from mapping just one source to needing two or more sources.
The 'select` methods on Bindings were not compile-time safe and IIRC used
reflection. I never used them. Subscribe made memory management easier and
avoided sneaky memory leaks (which can still happen, just less often).
There were very good reasons to include them. Here it's more a matter of
ergonomics.
On Tue, Oct 28, 2025 at 10:28 AM John Hendrikx <john.hendrikx at gmail.com>
wrote:
> Thanks for taking a look Nir, I really appreciate it :)
> On 26/10/2025 13:56, Nir Lisker wrote:
>
> When I need to combine observable values I usually do something like:
>
> Bindings.createDoubleBinding(() -> width.get() / height.get(), height,
> width);
>
> which is much less cumbersome than subclassing (although a bit less
> performant I think). It works for an arbitrary number of observables
> (including) observable lists/sets/maps too:
>
> You're right, I forgot about the existence of the Bindings class, and the
> helpers it has added for these cases. I barely use it since the addition
> of fluent bindings. That version is a lot more compact and a bit more
> workable.
>
>
> Bindings.createDoubleBinding(() -> list.getFirst() / height.get() *
> map.get("A"), height, list, map); // assume this makes sense somehow
>
> ReactFX, which is the go-to library for such extension, has a 'combine'
> method that work like this:
>
> Val.combine(height, width, (h, w) -> w / h);
>
> I'm aware of this, and it is something to be considered as an addition.
> It is similar to the argument where there is a `Subscription.combine` and
> `subscription.and`, one being static and the other being a fluent method.
> I think however we shouldn't be relying on ReactFX too much anymore these
> days (I haven't used it in years now). The primary reason for that is that
> ReactFX had to introduce new property classes which are limited in how well
> they can interop with existing JavaFX code. With the fluent bindings
> additions the need for ReactFX is limited. That is not to say ReactFX
> doesn't offer anything interesting anymore :)
>
> To assess the proposal, I tried to write your JEP examples with the
> current JavaFX.
>
> Multi-stage chain (not sure how you mapped to a Point3D from a Point2D and
> a number):
>
> ObjectBinding<Point2D> point2d = Bindings.createObjectBinding(() -> new
> Point2D(x.get(), y.get()), x, y);
> ObjectBinding<Point3D> point3d = Bindings.createObjectBinding(() -> new
> Point3D(point2d.get().getX(), point2d.get().getY(), z.get()), point2d, z);
>
> Combining chains:
>
>
> ObjectBinding<Point2D> point1 = Bindings.createObjectBinding(() -> new
> Point2D(x.get(), y.get()), x, y);
>
> ObjectBinding<Point2D> point2 = Bindings.createObjectBinding(() -> new
> Point2D(x.get(), y.get()), x, y);
> ObjectBinding<Line2D> line = Bindings.createObjectBinding(() -> new
> Line2D(point1, point2), point1, point2); // Line2D is not a JavaFX class
>
> Using a default value:
>
> ObservableValue<Point2D> point1 = Bindings.createObjectBinding(() -> new
> Point2D(x.get(), y.get()), x, y).orElse(Point2D.ZERO);
>
>
> Some observations:
> Bindings returns a Binding rather than an ObservableValue and also has
> primitive specialization versions that have their unique methods (add,
> subtract...). These methods, however, can be replicated with fluent
> bindings and they also have the "known" subtle GC issue for the
> intermediary values.
>
> Yes, I don't think we should cater to primitive specializations. Bindings
> tend to be high level enough (often eventually tied to a UI) that it makes
> little sense to "optimize" these at the cost of 8x more variants. The GC
> issues are often insidious, and my problems in that area have largely
> disappeared with the addition of the fluent bindings and subscribe API's.
> This is why I'm hesitant to use API's from the Bindings class, and why I
> think FX should offer alternatives in that area.
>
> Also, static imports can make these calls less ceremonious:
> createObjectBinding(() -> new Point2D(x.get(), y.get()), x, y);
>
> Static imports are not really a plus for any argument in my view :)
> Compare AssertJ and Hamcrest to see what I mean.
>
>
> The proposal is more ergonomic with its fluency for a couple of values,
> but I'm not sure it solves enough problems that the current mechanism can't.
>
> It is an extension on the fluent API, it doesn't introduce anything that
> isn't possible currently, just like `map` and `flatMap` didn't introduce
> anything new, nor did `subscribe`. It is intended to be a more
> discoverable, fluent and modern API, and to fill a gap where one has to go
> from the fluent binding model to a static helper class model as soon as you
> go from mapping just one source to needing two or more sources.
>
> --John
>
>
> On Sun, Oct 26, 2025 at 11:59 AM John Hendrikx <john.hendrikx at gmail.com>
> wrote:
>
>> JEP: https://gist.github.com/hjohn/611acb65769b68a845b8919c62a3e99a
>>
>> Hi everyone,
>>
>> I'd like to propose an extension to the fluent bindings API on
>> ObservableValue (map, flatMap, orElse) which were introduced in JavaFX
>> 19 over 3 years ago.
>>
>> The API currently is very powerful when dealing with a single
>> observable, but lacks support when dealing with multiple observables.
>> For example, let's say you want to compute a width/height ratio. You
>> could write this:
>>
>> ObservableValue<Double> ratio = width.map(w -> w / height.get());
>>
>> ... but you'll quickly find that such an observable will not update
>> itself when height changes, only when width changes.
>>
>> The go-to solution for this is currently:
>>
>> DoubleBinding ratio = new DoubleBinding() {
>> { bind(width, height); }
>>
>> protected double computeValue() { return width.get() /
>> height.get(); }
>> }
>>
>> My proposal would extend ObservableValue with a new `with` method that
>> returns an intermediate stage that can be easily converted back to an
>> ObservableValue:
>>
>> ObservableValue<Double> ratio = width.with(height).map((w, h) -> w /
>> h); // yields a ratio that updates whenever w or h changes
>>
>> Or for example:
>>
>> ObservableValue<Point> point = x.with(y).map(Point::new); //
>> yields a Point that updates whenever x or y changes
>>
>> The intermediate stage would not be an observable value itself. This
>> limits the API surface, and makes this proposal fairly lightweight and
>> much easier to implement.
>>
>> Please see the JEP for the full proposal. I look forward to your
>> feedback!
>>
>> --John
>>
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20251028/ca4ffa8b/attachment.htm>
More information about the openjfx-dev
mailing list