117 comments

[ 2.5 ms ] story [ 180 ms ] thread
I'm wondering if it's possible to merge it into lombok
There are good reasons lombok ExtensionMethods never left the experimental status.
They mostly have to do with design decisions / implementation details, not issues with extension methods in general.
I never saw the need for annotating extension methods like in Lombok or Manifold . Lombok is great for other stuff though.
There are good reasons. When you begin writing IDE support you will discover that:

1. Without annotations you’ll be forced to index all static methods based on the first argument, globally.

2. Method call code completion will be ridiculous. How many static methods have String, File, List, Object(!) etc. as the first parameter? I don’t think anybody wants their code completion to be invaded like that.

The annotations provide intention. Essentially, adding an extension method is not something to be taken lightly, it requires careful consideration, weighing context, frequency, etc.

IDEs are not my strong point. Aren't they smart enough to just search static methods in the scope of the compilation unit? Okay, so they have to be indexed first I suppose, but still... Ah, I suppose you want to search for methods you haven't imported yet. Now I get the annotation stuff. We're writing code for the IDE.
Looks similar to how you can use implicit classes in Scala[0]. They were definitely convenient for writing unit and integration tests, but difficult to read/maintain. I feel like a pipe operator would be a much more useful alternative instead of syntactic sugar for presenting functions as object method calls.

[0]: https://www.baeldung.com/scala/implicit-classes

Oof... looks more object-oriented hell to me. I really like the way functional programming languages separate types from functions, and I've been adopting this pattern in my Java code. Extension methods let you do this and keep your fluent api. Thanks for the link though, I didn't know about implicit classes.
We did Scala at an old job and various kinds of implicits got us into so much trouble. I think if you use them judiciously, they'll be fine, but you need quite a bit of experience and maturity to use them judiciously, which most of us lacked.
The usage for extension methods is very easy and safe, there is IDE support too. It's really not a problematic usecase for implicits.

But yeah, implicits are powerful and should be used carefully.

Scala 3 has actual extension methods.

    extension (c: Circle)
      def circumference: Double = c.radius * math.Pi * 2

    val c = Circle(2, 3, 5)
    c.circumference()
Nice, this is one of the features that makes using Kotlin with existing Java frameworks so nice. It can really cut down on the boiler plate Java is infamous for. You can define extension functions on any type, or type alias. So anything that looks like it could be shorter or is a bit repetitive, you just define an extension function to make that go away.
When you start transforming the AST, that might be a sign you should switch languages. Kotlin is nice, why not use it.
Kotlin is great, but it is a fairly heavy dependency if all you want is extension methods. Java has mostly caught up in many other aspects, so I'm experimenting with Java + one or two compiler hacks. Checked exceptions are my next target...
I'm curious about how you plan to work on checked exceptions.

If it's just about ignoring forced exception management, Lombok provides the @SneakyThrows annotation.

I would need something more before I add a new type of extension. Maybe if you could solve the null-check issue.

i.e. rewriting

    var x = a.getFoo()?.getBar()
to

    var x = a.getFoo() != null ? a.getFoo().getBar() : null
In that case I may consider it for my personal projects, and then potentially recommend it at work once it becomes more widespread and has IDE support.
I think Kotlin differs far too much from Java — I don’t want all those plus features that will inevitably branch too far from the JVM/Java corresponding code, becoming its new thing.

I might want a nearly-Java language that only adds trivial syntax sugar, but are otherwise the same. Groovy sort of had a good initial idea here (being a superset of Java, though again, it has grown apart a bit since).

For me Kotlin lives in this weird limbo between these two extremes. If I want a different language on the JVM I would rather do Scala or Clojure.

For me, if Kotlin is on the table I might as just use C# where the entire ecosystem is built around these concepts.
I wouldn’t really leave behind the JVM ecosystem, which is much bigger and better quality than the .NET one from what I gathered (though it is still good and bigger than most other languages’). And other people’s code is the numero uno productivity boost, so it’s an important area.
I think I would be fine with a smaller ecosystem. I've seen a lot of projects that have made bad choices in libraries because there are too many. Sometimes I feel like if there was just one blessed jdk way it would be a lot simpler. Honestly, the only thing that is keeping me from .NET right now is async/await. Pron really did Java a service by pushing for virtual threads.
How does this cut down on boilerplate? The examples given on the website seem equally long.

assertNotEmpty(getHttpContent(createSharedUrl(website, "styles.css")));

vs

website.createSharedUrl("styles.css").getHttpContent().assertNotEmpty();

Typically, boiler plate implemented by a standalone function is not easily discoverable, eg. no clear convention where to put it, does not look idiomatic on some usages syntax habits, and not suggested by auto completion in top results.

Extensions are searchable by syntax and naturally in a namespace, largely diminishing the risk of put it in in a single use function that will be quickly forgotten, and might seem obscure.

The code itself might not be much shorter, but you don't introduce code looking weird, if it's re-usable it's now discoverable, and if your boilerplate contained a lot of micro adjustments on a larger glue code, this glue code is now shorter and way clearer.

Also, creating a subclass just to add extension methods is definitely boiler plate. Util classes too, though they are useful for packaging functions at least.
Subclassing may not always be possible (final class, or method) and can break class invariants. Util classes with helper methods and/or extension methods are not prone to these problems.
> Extensions are searchable by syntax

This would be very useful. But how are these extension methods more searchable by syntax than the 'old fashioned' static util methods? I admit I haven't tried, but I suspect that code completion in my IDE wouldn't include these extension methods.

At least in Scala, code completion in the IDE does include available extension methods.
They're more discoverable, because typing in `object.` tells the IDE that you are looking for methods on the type of `object`. If the IDE is aware of extension methods, it can also expose them the same as instance methods.

Java specific: If you're looking for a static utility method, then `UtilClass.` will also autocomplete. However, what if the function you're looking for isn't in `UtilClass`, but `SomeOtherClass`? What if you don't even know whether a method already exists for what you're trying to accomplish, and it could possibly exist in multiple libraries? Your discoverability engine quickly becomes Google.

Does this mean I can extend the interface of existing, possibly final classes, e.g. to add extra (notional) methods to java.lang.String?
Extension methods have been available in C# since C# 3.0 (released 2007) and a great addition IMHO. In recent .NET versions, especially ASP.NET, they are heavily relied upon for core framework tasks. Wonder why Java never got them officially.
I can only speculate, but I think it is because extension methods can be shadowed if a vendor adds a method of the same name to the class you are extending. Of course, that depends on how you resolve methods.

In java, this would be a compile time problem, because once the class is compiled the extension method is called with `invokestatic` rather than `invokevirtual` used by class methods.

There is also a healthy fear of change in the java community that could explain it.

    There is also a healthy fear of change in the java community that could explain it.
Nice jab at the end! Java (well, really Sun, then Oracle) prioritises backwards compatibility over every other goal. So, does Win32 API. Does DotNet do the same? I'm unsure.

This also explains why it took so long for Java to get lambdas. And, generics with primitive types, e.g., ArrayList<int>, via Project Valhalla. It is a super hard problem to upgrade Java without breaking old stuff. Hence, it is a multi-year R&D effort.

Yeh, backwards compatibility is one of the biggest selling points of Java. Hopefully, Fluent will show that we can change one or two lines of compiler code and not burn down the whole house. We'll see.
Brian Goetz has written on the subject. https://stackoverflow.com/a/29494337

I think you can’t look at the past 5 years and the features that are being floated for Java 21 and future releases and think fear of change is the issue—the language is now changing rapidly.

Thanks for the link. I've been trying to find the canonical answer to this question myself. There's the philosophical standpoint: API designers should control their APIs, and the technical: poor reflective discoverability, poor discoverability through documentation, not overrideable, require ad-hoc conflict-management rules.

I like his use of the word should, because as we all know it really means won't. I should exercise more, I should stop trolling people on reddit. API designers should control their APIs.

As for the technical objections, I think the conflict-management rules hold the most weight, but this problem is no more difficult than any other in language design. "not overrideable" is a feature, not a bug (can we please give up on behavioural inheritance already?), and discoverability already sucks. I mean, I still use duckduckgo to find javadocs.

My sentiments exactly. The “API designers should control their APIs” one is my favorite and one of the more ridiculous things I’ve heard him say, which is saying something. It’s beyond humor, though, that all of his reasons are fallacies.
(comment deleted)
> No annotations are required.

I appreciate the intent, but man is this like a minefield waiting to be walked over. Anyone who looks at the code will scratch their head and will not understand what is going on. An annotation would perhaps make it clearer.

Also, how about IDE support? I would argue it is at least as important as the feature itself.

Yes, IDE support would help developers find the method that is being called. This is already quite a problem given we have complex class hierarchies and interfaces with default methods. I'm not sure I see the need for an annotation though.
A compile-time annotation could at least control whether your plugin is allowed to transform the ADT in the given context (say: for a class). Otherwise is it not possible that it transforms the ADT even when the user did not intend to? Can this be a source of mysterious bugs?
The syntax tree is only transformed when instance method lookup fails. i.e. when your code doesn't compile in standard java form. You would be doing pretty well to accidentally call an extension method seeing as you either have to write them or import them.
This whole what-if scenario is a complete non-issue. Many other languages do this out of the box and have no issues with it.
The key point is that they do it out of the box, so everyone is aware of it. As it is, it could easily interfere with some other non-standard extension plugin, for example.
Literally any code you write could interfere with some non-standard extension plugin, this is all pointless hot air.
Which is one of the reasons not many use non-standard extension plug-ins for their compilers.
I appreciate the intent, but man is this like a minefield waiting to be walked over. Anyone who looks at the code will scratch their head and will not understand what is going on. An annotation would perhaps make it clearer.

That is the generic argument against extension methods wherever they appear. They are very confusing, until you get used to remembering their existence, then you know what to search for.

Also, how about IDE support? I would argue it is at least as important as the feature itself.

This is mentioned in TFA.

Extension methods in a language that explicitly supports them and with good IDE support are perfectly fine. I love them in Kotlin. Extension methods as a compiler plugin that rewrites the AST... not so great. Great IDE support is a large part of what makes Java worth using, the last thing I need is to break that.
In C# world they somehow manage to do it without all of this annotation oriented programming
(comment deleted)
Just to make it clear, Fluent does not use any annotations.
Not really. First argument (the object you are extending) needs to be marked with `this`, which is an annotation.
Ehh, ive been talking about whole lang and ecosystem

But I agree about "this" being similar concept

There are tons of annotations over the hood.

The "this" that GP mentions is for for extension methods similar to the article to be regcognizeed as such (never done if the annotation isn't present), they're written such as:

static int Foo( this Bar bar, int biz) => bar.xyz+biz;

Bar bar = new Bar() { xyz=123; };

int result = bar.Foo(456); // Works even if Foo wasn't a part of Bar initially

Elsewhere annotations(called attributes in C# land) can often be used in the same way as in Java to document serialization behaviour,etc. So in practice there aren't much differences apart from how library authors haven't embraced it entirely as much for automagic bindings (partly due to philosophy, partly due to other facilities being more appropriate).

There is a massive difference between a feature universally offered by the language and something only supported by using a specific library of compiler plugin...
What a weird take. Parent obviously meant @X meta-programming annotations, not language syntax and keywords with a specific definition.

The definition that includes it is so broad that it loses all meaning, it would make "private", "class" or "boolean" also annotations.

Isn't this just saying some languages do things others do not?
No, annotations are available in both, yet one community uses them more often
So, "some languages do this with annotations, others don't need it". My point stands.

I mean it's fine to not like annotations or the decision to use them, but that another language does things differently is almost a tautology and hardly worthy of note. IMO, reasonable people will disagree.

Yes, it’s a slightly different language and should have a different extension, like js versus jsx.
Some languages allow declaring things like this per file

  "use strict";

  from __future__ import annotations

  {-# LANGUAGE TemplateHaskell #-}
which probably scales better than 2^n filename patterns.
Official upgrades to languages don't result in that kind of combinational explosion of language extension combinations because the features eventually become standard. They do result in a combinational explosion of language feature combinations, though, so there's still a problem with languages just getting big. IDE's and other language tools need to support it too.

In JavaScript there is .js, .ts, .jsx, and .tsx. I hope that doesn't become a lot more, but for low amounts of variation, it's reasonable.

I think Haskell is a good example of what happens when a language supports too many extensions. Why are there so many? How do people deal with them all? Needing to pick another file extension seems like pressure to do this more sparingly?

> is this like a minefield waiting to be walked over

"This looks odd to me so no one else can possibly understand it"is not a rigorous argument.

Both Scala and C# have this. I've used it in my own code to clean things up. It's fine.

I like the way static extensions are done in Haxe https://haxe.org/manual/lf-static-extension.html

Using macros you can auto enable them for all types in a project, package, or module.

And opposite to Dart you don't need to define the extension methods in special extension classes but regular utility classes or modules.

Interesting. Do you need to put the extension method in a utility class at all though? Could we put it in the Main class in the example?
The static method can be anywhere afaik.
> Fluent will make you a more productive programmer

Need to see some benchmarks for this, chief.

Still, cool hack, and it's heartening to see that there's enough extensibility to make this possible in 150 lines of code!

Another java fork like Lombok. But this time the authors didn't take the time to support all java compilers/IDEs, so the situation is even worse.

If this is just to highlight the merit of static extension methods with a follow up JEP I am all for it, but please don't advertise this as a feature for production. It's a trap.

Lombok is not a Java fork.
Well a language fork. But not a full implementation fork of course. Or would you argue that what Lombok does is part of the java spec? It effectively extends the java spec in non backwards compatible ways.
Do you really think a JEP is worth investing my time in? AFAICT, the Java gods have already made up their minds on extension methods.
I did some digging on past discussions, and yes, it seems the stars are not aligned for extension methods in standard Java.

But tbh, there are some very good arguments against extension methods. Also, some tricky questions about overloading handling and compatibility are lurking in the shadow. But if you have good suggestions on how to fix them, I'd take the time to propose something on an OpenJDK mailing list, maybe you get some support.

Can someone explain the rationale for not being able to call a static method on an instance? A lot of programming language do this, I assume there is a reason, but I don't understand what it is.
You can call a static method on an instance. This project allows you to call static methods not defined within the instance's class.
And what’s the benefit of that? Except maybe you can chain it and it looks a bit nicer
Tab completion is much better w/ (extension) methods.

I would pay like, $10,000 of my own money for extension methods like this (actually like Kotlin or Swift) in Typescript. That would solve so many abstraction design problems for me!!

Why is calling SomeObject.SomeFunc() arguably better than calling SomeFunc(SomeObject)?
I've used these a lot in C# and it is actually a lot better, and common, to write some extension methods for common operations (e.g. standardized formatting). It's very common to see an "Extensions" folder in a C# project, where these will live.

So what you didn't notice is that you if you need to share SomeFunc across classes, you'd have to put it in a static class, so it's actually SomeStatic.SomeFunc(SomeObject) vs SomeObject.SomeFunc(). So you end up with a load of Utility classes with tons of static methods on them, or you do extensions.

But mostly the advantage is that it's actually clearer when you're reading the code.

It can also add quite a lot to discoverability in the right circumstances, not sure abut Java, but in C# you can include Extensions in global namespaces and so it just turns up in intellisense, without needing to know there's a special utility class.

Visual Studio has actually shipped new functionality recently where it will suggest extension methods (and classes) not included in your usings anyway, and then automatically add the using statements if you choose to use them. I thought this new functionality would annoy me but it's pretty clear from the prompt which objects are already in scope, and I only find myself occasionally having to clean up the using statements.

TL;DR; Looks a trivial change, but it surpisingly improves the readability of code, and the discoverability of home-grown utility functions.

You're oversimplifying the use of continuation / builder pattern / "fluent" / monadic interfaces.

   SetReadTimeout(SetWriteTimeout(SetConnectTimeout(FactoryClass.factory(), 100), 2000), 4000);
vs.

   FactoryClass.factory().SetConnectTimeout(100).SetWriteTimeout(2000).SetReadTimeout(4000);

https://java-design-patterns.com/patterns/fluentinterface/
Nice example. The extra parameters really drive the point home. Hope you don't mind if I steal it for the README.
Or, in my opinion, even worse... When you get mix-and-match because you want to extend a fluent API. Using your example, what if `SetWriteTimeout` was a static method you wrote, but all the others were provided by library as a fluent API? Then you end up with this kind of unholy mess:

    SetWriteTimeout(FactoryClass.factory().SetConnectTimeout(100), 2000).SetReadTimeout(4000);
Good luck correctly guessing the order of operations on that in a quick read...
This, having to remember which methods are implemented inside the class and which ones should have been is almost like the colored function problem.
For me, any functionality that requires a compiler plugin, automatically raises the bar for adopting that functionality. Any extra features have to be contrasted with essentially modifying the language.
Yep. It will be a footgun for environments with and without JAVAOPTs.

Better off transforming .java code at compile-time into .class'es in a .jar that always works at runtime.

Fluent transforms the abstract syntax tree at compile-time resulting in .class'es in a .jar that always work at runtime. There are no runtime dependencies.
Oh shit, you're right. I thought it was java and not javac. My middle night toilet writing on HN failed. :) It makes much more sense because "why would you have a plugin at runtime and not just pass a classpath or jar?" Still, maven and all tools, including language servers, that could compile or interpret it outside of a pom.xml would need that detail or they would break.

This topic makes me think of what other sort of compile-time mutation plugins could be had, including streamlining customizations with a per-project convention. Maybe Erlang bit syntax and matching for Java?

The JDK team won't like this. As it is Fluent, Lombok, and (I presume) Manifold are using all sorts of unsupported hacks to access compiler internals. If you restrict yourself to the compiler plugin API you don't get much mileage.
I would only use this if I couldn’t use Kotlin, which has excellent extension method ergonomics that compile to basically the same thing as this.
"Fluent interfaces" are nothing new and can be (and are in some projects) used in Java. When done correctly code completion works fine.

As to whether they're worth the addition trouble or not, I sure as heck don't know.

But it's definitely nothing out of the ordinary.

    blobs = createBlob()
        .withinRange(min,max)
        .excluding(a,b,c)
        .asArray()
There are variations, for example where only the last call actually creates, say, a Blob array and where every intermediate call produces something only allowing the next method to be called, so that the IDE autocompletes nearly everything for you and where you won't be able to compile if you miss a single call.

Don't know if the following link is any good but "fluent interfaces" are 15 years old stuff at least by now I'd say:

https://java-design-patterns.com/patterns/fluentinterface/#e...

How do you do the same thing to java.lang.String?
A class MyStringBuilder with domain-specific fluent interface can do the job perfectly.
and a MyStringBuilderInterfaceProxyFactory too of course
Hate towards design patterns is a typical sign of not understanding them. Having a builder class to construct a certain subset of generic objects is a normal design practice, so your irony does not look appropriate and does not contribute to the discussion.
This example does not look convincing:

   website.createUrl("styles.css").getHttpContent(60).assertContains("img.jpg");
My preference would be:

   var stylesheet = http.get(website.resolve(“style.css”)).asString();
   assertThat(stylesheet, contains(“img.jpg”));
Why? Fluent interfaces are good as internal DSL, where semantics of calls are clear and where readability is improved, because it looks closer to the natural language. This means that subject remains the same if you use multiple verbs. Just chaining several imperative statements does not add much value and only complicates debugging.
.resolve seems like the wrong verb here though.

What are you passing into http.get? a url, not something resolved.

thus pattern divorces the code from the program ime

i appreciate that using an ide makes code navigation easier but i reject the notion that you should need one to navigate the code

(comment deleted)
Ever noticed how whole industries such as this exist merely to solve the incidental complexities of OOP?
I feel like this misses the reason I like extension methods: discoverability.

With an extension method, I can do `object.` and my IDE will tell me what can be called on object. With a static helper method, it isn't as easy to know what is available. I need to know which helpers actually exist.

Since this doesn't have IDE support, it doesn't help discoverability. I'm not going to get nice autocomplete that shows me what is available. In fact, my IDE is going to highlight it as a bug. If I have a spelling mistake, I won't be able to easily pick it up - I'll assume it's just the normal complaint for all of these fluent extension methods.

That makes this simply syntactic sugar rather than something that actually helps me discover things more easily. It then hurts readability and navigation since I can't easily click through to get the definition of the method.

On a more general note about Java, things like this are one of the reasons I don't love the Java ecosystem. People try to change the behavior of Java in really hacky ways that don't work well. I understand that it's an attempt to overcome shortcomings in the language, but when one looks other languages it becomes clear that Java could have just evolved the language to be better. Java has lots of good things and I'm not looking to argue that. However, when I look at things like this, it makes me think that Java needs to really address the core language.

Instead, we get lots of tools like this which might be nice, but make it really hard to understand what's going on. Electronic Arts created an async/await library that'll do crazy stuff to let you do async/await style programming (https://github.com/electronicarts/ea-async). Yes, Java is doing good things with structured concurrency and Project Loom, but the point is how people keep trying to work around the language. There are so many POJO generators it isn't funny: AutoValue, Immutables, JodaBeans, Lombok, and more I'm probably forgetting. Java records don't fulfill everything (and they're at least a decade late). Java doesn't support expression trees for lambdas so libraries sometimes do crazy hacky things to make that exist.

Java is a great piece of technology, but it feels like people are often trying to overcome issues with the language through really hacky means in a way that I don't see in other languages. Java is getting better about modernizing the language, but it still feels like people are running against the language more than in other ecosystems.

Can you elaborate on what you mean by expression trees for lambdas?
This is "cool", and really demonstrates the extensibility of the language, but definitely a nuclear minefield and something I would never use for anything other than a demonstration.

This really isn't solving any real problems either. The first set of syntax shows definitive programmer intent at the cost of.... 4 extra keyboard presses. The code will also fail compilation or even at runtime if some API changes in a dependency, which is a _good thing_.

This reads like Lombok all over again.

If you want extension methods on the JVM, do yourself a favor and pick kotlin.

I know I know, stuff like this exists for when corporate policy insists on sticking with java, but a corporate policy that at the same time rejects kotlin but allows bastardizing java like this it's a broken corporate policy. The good news however is that stuff like this can be an excellent tool to put pressure on that kind of policy ("if we don't get to use the good stuff we'll use the bad stuff!") and if it was made for that use case, I'll wholeheartedly applaude.

That being said, I'd really, really love to see the kotlin treatment applied to other languages, stuff that doesn't have actual kotlin waiting on the sidelines. Once I got used to the ease of left-to-right provided by kotlin context method funnels it's really painful to go back to nested if blocks (even in languages like typescript that apply the type inference of the conditional to the nested block just fine).

This makes me want to try out "ellipsis" arguments. The ellipsis tells the compiler that the intended arguments for a function are the only ones available in scope that agree with the types required for the function, so they can be inferred.

static URL buildSiteURL() {

  Website website = getMyWebSite();

  String path = "styles.css";

  return createUrl(...);

}