Language feature to support Fluent Syntax for Static Method Calls
Kristofer Karlsson
krka at spotify.com
Tue Mar 28 12:37:13 UTC 2023
Hi, I am new to this list so apologies if I'm missing any guidelines to follow.
This feature idea includes a specific solution, but I think the
feature (some way to support a more fluent code flow with methods that
are not part of the object) is more important than the solution
(Allowing a new syntax for static method calls) so even if the
solution ends up being rejected, I hope the feature itself does not
have to be.
I have tried searching for existing JEPs that roughly matched this
feature idea but could not find anything. Before trying to create an
actual JEP, I thought it would be useful to know if this is a
reasonable idea at all. The closest thing I've been able to find is
this stackoverflow post[1] where extension methods are discussed.
However, I'm thinking about this problem in terms of enabling a more
fluent code style at the use-site without changing any semantics, and
not in terms of designing and controlling APIs (for which the existing
interface abstractions already work very well).
[1]: https://stackoverflow.com/questions/29466427/what-was-the-design-consideration-of-not-allowing-use-site-injection-of-extensio/29494337#29494337
Best regards
Kristofer
---
Title: Fluent Syntax for Static Method Calls
Author: Kristofer Karlsson
Created: 2023/03/27
Type: Feature
State: Draft
Exposure: Open
Component: specification / language
Scope: SE
Discussion: amber dash dev at openjdk dot java dot net
Template: 1.0
## Summary
Enhance the language by allowing static method calls to be used in a fluent way.
## Goals
Enhance the language and compiler to support invoking static methods
in a fluent way. This would be a purely ergonomic change to help
developers to write code that is easier to read, write and maintain
without breaking any existing code and without requiring any changes
to the bytecode or the runtime.
Fluent in this context refers to https://en.wikipedia.org/wiki/Fluent_interface
## Motivation
Given the JDK additions of streams and classes such as CompletableFuture, and
already existing patterns of using builders and operating on immutable types,
writing code in a fluent style has become common, and is considered best
practice in many cases.
For streams you would do something like:
list
.stream()
.map(x -> x + 1)
.limit(10)
.collect(Collectors.toSet())
and with CompletableFuture you would do something like:
CompletableFuture.completedFuture("hello")
.thenApply(s -> s + " world")
.exceptionally(e -> "An error occurred")
.thenRun(this::someCallback);
These classes and interfaces are out of our control - they are part of the JDK -
so we can not extend them to add other potentially useful methods such
as Stream.parallel(executor), Stream.toSet(),
CompletableFuture.handleCompose(func),
CompletableFuture.orTimeout(time, unit, executor).
We can create static methods that operate on such classes and return
new instances,
but chaining such calls together becomes clunky. Here's a (somewhat contrived)
example of what it would look like today vs what it would look like with
the proposal in place:
toSet(parallel(list.stream(), myExecutor)
.map(x -> x + 1)
.limit(10))
compared to:
import static MyStream.parallel;
import static MyStream.toSet;
list.stream()
.parallel(myExecutor)
.map(x -> x + 1)
.limit(10)
.toSet()
And for CompletableFuture:
orTimeout(
handleCompose(
flatten(
CompletableFuture.completedFuture(null)
.exceptionally(e -> new CompletableFuture<>())),
(v, e) -> ...),
10, TimeUnit.SECONDS, myExecutor)
compared to:
import static MyFutures.flatten;
import static MyFutures.handleCompose;
import static MyFutures.orTimeout;
CompletableFuture.completedFuture(null)
.exceptionally(e -> new CompletableFuture<>())
.flatten()
.handleCompose((v, e) -> ...)
.orTimeout(10, TimeUnit.SECONDS, myExecutor)
Note: CompletableFuture.exceptionallyCompose is an example of a method that was
initially missing but later added due to being convenient, even though it could
have been implemented as a simple chain of calls:
future.thenApply(CompletableFuture::new)
.exceptionally(e -> func(e))
.thenCompose(Function.identity())
# Description
Currently static method calls look like:
ClassName.methodName(arg1, arg2, ...);
Combined with a static static import statement they can also look like this:
import static ClassName.methodName;
methodName(arg1, arg2, ...);
The compiler could be modified to also recognize this form:
import static ClassName.methodName;
arg1.methodName(arg2, ...);
For simplicity, this would only be allowed for statically imported methods.
This means that you could invoke Arrays.fill() as:
import static java.util.Arrays.fill;
int[] array = ...;
array.fill(0);
## Out of scope
Technically, this feature could also be allowed for primitives. This
would mean that
you could invoke Math.abs(-1L) as (-1L).abs() and Math.exp(2.0, 3.0)
as (2.0).exp(3.0).
However, this may introduce extra complexity for the compiler to
handle, so for the purpose
of simplifying the JEP, this is out of scope.
## Compatibility
All existing valid code will continue to be valid and behave exactly as before.
## Risks
If new code introduces usage of this new method invocation, there could be a
subtle change to the method resolution if the argument type introduces a
method that matches the signature. Recompiling the class after the argument type
dependency has been updated would lead to a different method resolution.
However, this is already true for a similar case:
public class A {
public static void main(String[] args) {
(new B()).theMethod();
}
}
public class B {
static void theMethod() { }
}
will show that invoking B.theMethod() resolves to invokestatic:
8: invokestatic #4 // Method B.theMethod:()V
If B is then changed to:
public class B {
void theMethod() { }
}
and we recompile A, we get:
7: invokevirtual #4 // Method B.theMethod:()V
If we don't recompile A, we get this error instead:
Exception in thread "main" java.lang.IncompatibleClassChangeError:
Expected static method 'void B.theMethod()'
at A.main(A.java:3)
More information about the amber-dev
mailing list