Osmerion / Quitte

Quitte provides specialized observable properties and expressions with lazy variants and observable collections.
BSD 3-Clause "New" or "Revised" License
2 stars 0 forks source link

Easy arithmetic expressions #2

Open SkytAsul opened 1 year ago

SkytAsul commented 1 year ago

I may have overlooked the library, but it seems like there is no arithmetical expressions embedded in Quitte, similar to the Observable<Number>#add(...) or Observable<Number>#isEqualTo(...) methods in javafx-base (https://github.com/openjdk/jfx/blob/master/modules/javafx.base/src/main/java/javafx/beans/binding/NumberExpressionBase.java). It may be interesting to have those :)

TheMrMilchmann commented 1 year ago

Hi! I'm aware that it is currently cumbersome to express some calculations due to the lack of a fluent API. This includes (but is not limited to) arithmetic expressions. I prototyped a few different approaches in the past but wasn't all too happy with any of the results. The reason behind that is adding these methods is not actually as easy as one might think because there are a few design considerations and decisions that are important to get right:

  1. Let's consider what ObservableInt.plus(ObservableInt other) should return? In Quitte terminology, this would be an expression (and the expression API would be used for the implementation) but should this be exposed in the API? Returning an expression would currently not expose any interesting additional functionality but that might change in the future. However, that question is not even the main culprit here, so let's continue.

  2. Should the result of the operation be eagerly evaluated ("simple") or lazily evaluated? There are pros and cons to both approaches. Returning a lazy expression would make sense to avoid the computations until they are actually used and, although that might not matter too much for basic arithmetic operations, we are setting a precedent for future functionality. Consider the method:

    public class ObservableList<E> {
        public ObservableLongValue sumBy(ObjectToLongFunction<E> selector) { ... }
    }

    The method would iterate over all elements in the list, perform a computation to select a number for each one, and sum them up. As lists may grow larger, surely that's something we don't want to be recomputed unless it is needed. However, picking lazy values by default may negatively impact property implementations with side effects.

  3. Finally, a fluent API encourages writing complex expressions by chaining method calls which is generally undesirable as the intermediate objects have a noticable overhead.

Since I have not yet come up with a solution that satisfies all of these requirements, I have opted not to introduce such an API in the past. However, I'm looking to revisit some old decisions and new ideas to design an API that offers the same flexibility and convenience with fewer drawbacks.

SkytAsul commented 1 year ago

Alright thanks for you detailed answer!