Extension methods
Johannes Spangenberg
johannes.spangenberg at hotmail.de
Sun Apr 28 10:43:59 UTC 2024
Here are a few notes from my side, although I am just a fellow
subscriber to the mailing list and not an API designer of the Java project:
> 2. Documentation accessibility is a strange point for me to be fair.
> Every IDE nowadays is capable of fetching the right documentation, as
> well as explicitly mentioning where the method comes from, as it is
> done in C#, kotlin, swift and many other languages. I don't think
> anyone has ever heard complaints about poor documentation of LinQ.
> Unless someone is writing in notepad, this is poorly applicable.
I am regularly annoyed by this issue whenever I read source code at
GitHub which uses Kotlin, C++, JavaScript, or on-demand imports in Java.
I my view, it is less about finding the documentation, and more about
finding the definition of the method. People are not always in their
feature-rich IDE when reading source code.
> Regarding ambiguity, the common practice (which also is applied in my
> design) is to prioritize members over extensions, there is no
> ambiguity if behaviour is well defined.This "potentially" could
> sometimes result in source incompatibility with some third-party
> libraries, but as soon as this is not conflicting with anything from
> stdlib, that's not really our concern. Also, I think it's kind of
> exotic scenario, and may crash only with utilities from stdlib
> classes, which cuts off virtually all production code from the risk group.
Regarding the related risk of collisions, I recently was affected by the
following Gradle bug, which is caused by a collision between the stdlib
of Java and Kotlin (made possible by extension methods):
https://github.com/gradle/gradle/issues/27699
> 3. Not overridable. Should they be? I don't think there is a way to
> achieve some kind of "polymorphic" extensions, and I don't think there
> should be: extension methods should provide polymorphic target
> handling, floow LSP etc., not the other way around.
Rust uses a single concept (Traits) which effectively supports both.
Traits also provide a nice solution for the issues behind Unions.
However, while I would like to see something similar to Traits in Java,
I would actually cut the extension-method-semantic. (i.e. I would make
the methods available only after the object was assigned to the type of
the Trait.)
For people who don't know Rust, you can think of Traits as interfaces,
which can also declare implementations for already existing classes.
Here is an attempt to map it to some made-up Java syntax:
/* usage */
MyFilesTrait files1 = Path.of("src")
MyFilesTrait files2 = List.of(Path.of("src-1"), Path.of("src-2"))
/* trait definition */
interface MyFilesTrait {
Stream<Path> asPathStream();
for-class Path {
@Override
Stream<Path> asPathStream() {
return Stream.of(this);
}
}
for-class File {
@Override
Stream<Path> asPathStream() {
return Stream.of(this.toPath());
}
}
for-class Collection<? extends MyFilesTrait> {
@Override
Stream<Path> asPathStream() {
return this.stream().flatMap(item -> item.asPathStream())
}
}
}
If you would seal the interface, you would effectively have an union.
> Its common issue users just aren't aware of the existence of certain
> utility classes and end up with nonoptimal code.
This seems mostly related to the auto-completion of the IDE. I don't
think the extension functions would be necessary for that, but I agree
that extension methods would guide IDEs and kind of force them to
implement the discovery during auto-completion.
> Code without extension methods is always much more verbose, if the API
> is supposed to be fluent developer could end up with deep nested
> invocations.
> as for "the illusion of belonging" it could be addressed by
> introducing some special operator instead of dot to highlight the
> difference, e.g. something like:
> x¬f3("p3")¬f2("p2", "p22")¬f1("p1")
In the JavaScript community, there were some discussions about
introducing a "pipe operator" some years ago, but it seems to be stalled
right now.
https://github.com/tc39/proposal-pipeline-operator
> JS also has its own unique way of extending APIs. The only modern
> widely-used language that does not have extension methods is Python,
> but that only applies to built-ins, custom classes could be extended
> as well.
If you are referring to the possibility to assign new properties to
prototypes (JS) or classes (Python), than I think it is considered bad
practice in parts of both communities.
From my point of view, extension functions as implemented by Kotlin are
not really a good fit for Java. Java and Kotlin have greatly different
design philosophies. While Kotlin focuses more on convenience (or ease
of use), Java has a much bigger focus and language simplicity. The
complex method resolution required for this style of extension functions
doesn't seem to fit well for Java in my opinion. Other solutions like
the pipeline operator seem like a better fit to me. While they also
increase the complexity by introducing a new operator, it keeps the
method resolution quite simple and doesn't hide the complexity as done
by extension methods. As a result, it also doesn't suffer from the
accessibility issues of extension methods.
Best Regards,
Johannes
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20240428/ae3711a6/attachment.htm>
More information about the amber-dev
mailing list