Fluent: Static Extension Methods for Java
github.com
github.com
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.
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.
The definition that includes it is so broad that it loses all meaning, it would make "private", "class" or "boolean" also annotations.
But I agree about "this" being similar concept
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).
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.
"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.
"use strict";
from __future__ import annotations
{-# LANGUAGE TemplateHaskell #-}
which probably scales better than 2^n filename patterns.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?
But yeah, implicits are powerful and should be used carefully.
extension (c: Circle)
def circumference: Double = c.radius * math.Pi * 2
val c = Circle(2, 3, 5)
c.circumference()assertNotEmpty(getHttpContent(createSharedUrl(website, "styles.css")));
vs
website.createSharedUrl("styles.css").getHttpContent().assertNotEmpty();
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.
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.
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.
The website examples aren't very inspiring indeed. Have a look at some of the extension functions for the Collections interface:
https://github.com/JetBrains/kotlin/blob/master/libraries/st...
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.
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.https://github.com/manifold-systems/manifold/tree/master/man...
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.
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.
Fluent will make you a more productive programmer, which may go against corporate policy.
Ha!
Anyway, nice work. Similar to D's uniform function call syntax.
https://tour.dlang.org/tour/en/gems/uniform-function-call-sy...
There's some more use cases in that link too.
Better off transforming .java code at compile-time into .class'es in a .jar that always works at runtime.
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?
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_.
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.
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.
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.
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.
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.
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.
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.What are you passing into http.get? a url, not something resolved.
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).
static URL buildSiteURL() {
Website website = getMyWebSite();
String path = "styles.css";
return createUrl(...);
}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...
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!!
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!
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.
SetReadTimeout(SetWriteTimeout(SetConnectTimeout(FactoryClass.factory(), 100), 2000), 4000);
vs. FactoryClass.factory().SetConnectTimeout(100).SetWriteTimeout(2000).SetReadTimeout(4000);
https://java-design-patterns.com/patterns/fluentinterface/ SetWriteTimeout(FactoryClass.factory().SetConnectTimeout(100), 2000).SetReadTimeout(4000);
Good luck correctly guessing the order of operations on that in a quick read...i appreciate that using an ide makes code navigation easier but i reject the notion that you should need one to navigate the code
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.