<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    <font size="4"><font face="monospace">To Ron's excellent answer,
        I'll add a few more points about things already
        considered-and-rejected.  <br>
        <br>
         - As the linked SO post indicates, we explicitly decided
        against extension methods on the basis that they are a _bad
        idea_.  This differs from many features that might be reasonable
        ideas, but just don't rise to the level of worth changing the
        language for.  Extension methods are a feature that explicitly
        subverts one of the important values of the platform -- among
        their many other weaknesses, such as poor discoverability and
        non-extensibility.  <br>
        <br>
         - During the development of the streams API, we did consider
        the question of extensibility, and whether some additional
        support for chaining static operations was valuable, similar to
        the "thrush" operator in Clojure
        (<a class="moz-txt-link-freetext" href="http://blog.fogus.me/2010/09/28/thrush-in-clojure-redux/">http://blog.fogus.me/2010/09/28/thrush-in-clojure-redux/</a>). 
        This is a more explicit syntax for "call this thing with the LHS
        as the first argument", something like `stream |> map(...)
        |> filter(...)`.  As Ron pointed out, while this holds out
        the promise of extensibility, it would not actually offer a lot
        of incremental power for streams, because the ability to
        "extend" streams with static methods is in reality quite
        limited.  So at the time, this seemed mostly a "shiny feature
        idea in search of a real problem."  <br>
        <br>
        There are a number of feature ideas centering on method chaining
        that keep coming around every few years.  My opinion is that
        they are mostly "weak" features, because method chaining itself
        is pretty weak.  In the cases where it works out, it is pretty
        nice, but the set of cases where it is actually a useful
        structuring convention for APIs is quite limited, and it is
        often badly overused.  (A prime example is "async" libraries;
        these illustrate the pitfalls of designing APIs around an idiom
        that only permits linear composition.  The simplicity of
        "filter-map-reduce" quickly gives way to becoming a programming
        straitjacket.)  <br>
        <br>
        Further, the call for more features around chaining often
        centers around superficial aesthetics rather than
        expressiveness, because chaining itself runs out of expressive
        ability quickly enough.  So a feature whose primary goal is
        "moar chaining" is suspect out of the gate.  <br>
        <br>
        <br>
        <br>
      </font></font><br>
    <div class="moz-cite-prefix">On 3/28/2023 8:37 AM, Kristofer
      Karlsson wrote:<br>
    </div>
    <blockquote type="cite" cite="mid:CAL71e4PCS369JCfsOh_BFQ4t-MQBf8_u1+ospoRu3XhcUL7d8A@mail.gmail.com">
      <pre class="moz-quote-pre" wrap="">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]: <a class="moz-txt-link-freetext" href="https://stackoverflow.com/questions/29466427/what-was-the-design-consideration-of-not-allowing-use-site-injection-of-extensio/29494337#29494337">https://stackoverflow.com/questions/29466427/what-was-the-design-consideration-of-not-allowing-use-site-injection-of-extensio/29494337#29494337</a>

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 <a class="moz-txt-link-freetext" href="https://en.wikipedia.org/wiki/Fluent_interface">https://en.wikipedia.org/wiki/Fluent_interface</a>

## 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)
</pre>
    </blockquote>
    <br>
  </body>
</html>