RFR: 8317993: Add capturing factories to classes in java.util.function package [v5]
Maurizio Cimadamore
mcimadamore at openjdk.org
Wed Oct 18 17:38:28 UTC 2023
On Wed, 18 Oct 2023 09:00:09 GMT, Per Minborg <pminborg at openjdk.org> wrote:
>> This PR proposes to add a number of "capturing factories" in classes in the `java.util.function` package.
>>
>> The PR additionally (an optionally) proposes to add a new function `UnaryOperator::andThenUnary` to allow composition while retaining the `UnaryOperator` type.
>>
>> With the new changes, it is possible to write code like this (example from `java.util.function.Function`):
>>
>>
>> // Resolve ambiguity
>> var function = Function.of(String::isEmpty); // Function<String, Boolean>
>> var predicate = Predicate.of(String::isEmpty); // Predicate<String>
>>
>> // Fluent composition
>> var chained = Function.of(String::length) // Function<String, Integer>
>> .andThen(Integer::byteValue); // Function<String, Byte>
>>
>>
>> Please see the original bug report for a comprehensive description of these proposed changes.
>>
>> Note: It is not the objective to promote `var` declaration or to prevent previous ways of capturing lambdas and method references. The comments in the code above is for explaining the binding and once that becomes obvious, such comments are likely to not appear in real code. Users that prefers having a normal type declaration can still do that.
>>
>> Note: Functional interfaces of primitives have not been considered (in this round). Otherwise, functional interfaces that might be ambiguous or that supports composition have been included. Hence, `Supplier` did not get a factory method.
>
> Per Minborg has updated the pull request incrementally with one additional commit since the last revision:
>
> Pretty
I'm a little concerned that these methods will prove less useful than it seems (or _differently_ useful). That is, the main reason for adding them would be to allow fluent composition of operators, functions, etc. But this functional composition has limitation: since the target type only applies to the _last_ call in a chain, all the intermediate call will be type-checked without a target type. This means that any attempt to use implicit lambdas, or inexact method references in the intermediate links or the composition chain will result in inference errors and will, in my opinion, provide a not very smooth user experience.
So, if we exclude method chaining, the other reason as to why the new factories might be useful is that they allow developers to write slightly more succinct code, especially when combined with `var`. E.g.
BinaryOperator<Integer> bop = Integer::sum
vs:
var bop = BinaryOperator.of(Integer::sum)
(of course the comparison gets more and more favorable for the latter the more type arguments appear in the functional interface type used in the LHS).
What about the vastly more common case where a functional interface is accepted as parameter to a Java method? Well, here it doesn't feel like the new factories add that much because:
* if no composition is needed, then target-type inference does its job just fine (e.g. just pass a lambda, or a method reference to the method)
* if composition is required, then, for the reasons above, the fluent style is hit and miss, and lack of target-typing support across the _entire_ method chain will result in inference errors.
So, I suspect I'm not 100% sold on the usefulness of these new factories.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/16213#issuecomment-1769025400
More information about the core-libs-dev
mailing list