JDK-8317993 : Add capturing factories to classes in java.util.function package
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.util
  • Priority: P4
  • Status: Resolved
  • Resolution: Withdrawn
  • Submitted: 2023-10-12
  • Updated: 2023-10-23
  • Resolved: 2023-10-23
Related Reports
CSR :  
Relates :  
Description
Summary
-------

Introduce _capturing factories_ in classes in the `java.util.function` package.

Motivation
-------

Lamdas and method references can often match several functional interfaces and so they have to be bound to a certain functional type using an explicit type declaration or a cast.

Consider the following class that contains a method inverting the value of a provided `boolean` parameter and two other methods that takes distinct functional parameters:

```
     public class Capturing {

         static boolean not(boolean original) {
             return !original;
         }

        static <T,U> void foo(Function<T, Boolean> f)
            // Do stuff
        }

        static <T> void foo(Predicate<T> p) {
            // Do stuff
        }

}
```

We can turn a method reference or a lambda involving this method into several distinct functional types using explicit declaration as exemplified here:

    Function<Boolean, Boolean> function = Capturing::not;
    Predicate<Boolean> predicate = Capturing::not;
    UnaryOperator<Boolean> unaryOperator = Capturing::not;

    Function<Boolean, Boolean> functionLambda = b -> Capturing.not(b);

    Function<Boolean, String> composed = function.andThen(Objects::toString);

    Capturing.foo(function);

However, if we want to use local variable type inference using `var` declaration or if we want to use method reference composition fluently, we need to resort to explicit casting as these examples show:

   var ambiguous = Capturing::not; // Fails!
   var function = (Function<Boolean, Boolean>) Capturing::not;
   var predicate = (Predicate<Boolean>) Capturing::not;
   var unaryOperator = (UnaryOperator<Boolean>) Capturing::not;

    var functionLambda = (Function<Boolean, Boolean>) b -> Capturing.not(b);

    // Fluent, not using a previously captured functional type
    var composed = ((Function<Boolean, Boolean>) Capturing::not).andThen(Objects::toString);

    Capturing.foo((Function<Boolean, Boolean>) Capturing::not);

This can sometimes cause inconveniences and excessive code that is hard to read. There is a need for a better solution.

Description
-------


Functional interfaces like `java.util.function.Function` receive a new type of static factory called a _capturing factory_ that allows binding and the engagement of type inference in a concise way. Here is an example of how such a factory might look like:

    /**
     * {@return a representation of the provided {@code uncaptured} lambda or method reference in the form of a Function}
     * 
     * @param uncaptured to capture
     * @param <T> the type of the input to the function
     * @param <R> the type of the result of the function
     */
    static <T, R> Function<T, R> of(Function<T, R> uncaptured) {
        return uncaptured;
    }

At first glance, the method might look redundant. However, the introduction of functions like these allows us to substantially improve code readability in certain cases. Here is how `var` declarations and fluent composition could look like with the new factory methods:

    var function = Function.of(Capturing::not);
    var predicate = Predicate.of(Capturing::not);
    var unaryOperator = UnaryOperator.of(Capturing::not);

    var functionLambda = Function.of(b -> Capturing.not(b));

    // Fluent, not using a previously captured functional type
    var composed = Function.of(Capturing::not).andThen(Objects::toString);
    var composedLambda = Function.of((Boolean b) -> !b).andThen(Objects::toString);

    Capturing.foo(Function.of(Capturing::not));

As can be seen, there is no longer any need to resort to casting, and the generic types are inferred automatically by the compiler. In short, the code becomes much more readable and concise.

It is possible to add a capturing factory to all classes in the package or only to such functional interfaces that, in combination with at least another functional interface, introduce ambiguities AND to functional interfaces that support composition.

Alternatives
-------

Keep using explicit declarations and casts.

Risks and Assumptions
-------

Code working with "ofers" like `Function.of(User::name)` may suddenly fail if overloads are added at a later time. This is something that we are already exposed to via, for example, the `Predicate::not` factory.

Dependencies
-------

None.
Comments
Adding factories as proposed would create compatibility issues when new overloads are added to method references used as an argument to the factories. It is better to add better type inference and perhaps introduce diamond operators for casts. Hence, this issue is withdrawn.
23-10-2023

[~liach] c.f http://minborgsjavapot.blogspot.com/2023/08/java-new-draft-jep-computed-constants.html
17-10-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/16213 Date: 2023-10-17 08:05:09 +0000
17-10-2023

[~liach] if you want laziness, you'd have to move the `.get()` operation to where the static field is used and also declare the static field as a Supplier.
12-10-2023

On a side note, this capturing method would be helpful for static field initializers in some code styles, such as: static final String MY_STRING = Supplier.of(() -> { // compute the string in a complex way }).get(); or static final Map<SomeEnum, String> MY_PROPERTIES = Function.of(m -> { m.put(SomeEnum.SOME_KEY, "value"); return m; }).apply(new EnumMap<>(SomeEnum.class));
12-10-2023