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