9 ms·
Fluent: Static Extension Methods for Java
- _ZeD_ 3y agoI'm wondering if it's possible to merge it into lombok
- _ZeD_ 3y agowait, it's like https://projectlombok.org/features/experimental/ExtensionMethod https://projectlombok.org/features/experimental/ExtensionMet...
- sam_lowry_ 3y agoThere are good reasons lombok ExtensionMethods never left the experimental status.
- zeedude 3y agoThey mostly have to do with design decisions / implementation details, not issues with extension methods in general.
- rogerkeays 3y agoI never saw the need for annotating extension methods like in Lombok or Manifold . Lombok is great for other stuff though.
- zeedude 3y agoThere 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.
- rogerkeays 3y agoIDEs 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.
- zeedude 3y agoDing!
- kitd 3y agoKnown Issues 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-syntax-ufcs https://tour.dlang.org/tour/en/gems/uniform-function-call-sy... There's some more use cases in that link too.
- rogerkeays 3y agoThis! It even has a fancy name to satisfy the purists.
- bilalq 3y agoLooks 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 https://www.baeldung.com/scala/implicit-classes
- rogerkeays 3y agoOof... 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.
- twic 3y agoWe 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.
- valenterry 3y agoThe 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.
- vips7L 3y agoScala 3 has actual extension methods. extension (c: Circle) def circumference: Double = c.radius * math.Pi * 2 val c = Circle(2, 3, 5) c.circumference()
- sjrd 3y agoScala 3 has actual `extension` methods, which have a much clearer intent. https://docs.scala-lang.org/scala3/book/ca-extension-methods.html https://docs.scala-lang.org/scala3/book/ca-extension-methods...
- jillesvangurp 3y agoNice, 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.
- MrBuddyCasino 3y agoWhen you start transforming the AST, that might be a sign you should switch languages. Kotlin is nice, why not use it.
- rogerkeays 3y agoKotlin 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...
- javanonymous 3y agoI'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.
- rogerkeays 3y agoI don't know yet. I'm already using some of my own variations of sneakyThrows, but what I really want is just to disable the whole damn thing in the compiler (checked exceptions are a compiler feature, not a JVM feature). I won't be changing the Java syntax though, sorry!
- naruhodo 3y agoDoes this mean I can extend the interface of existing, possibly final classes, e.g. to add extra (notional) methods to java.lang.String?
- rogerkeays 3y agoYes.
- littlecranky67 3y agoExtension 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.
- rogerkeays 3y agoI 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.
- throwaway2037 3y agoThere 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.
- rogerkeays 3y agoYeh, 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.
- hyperpape 3y agoBrian Goetz has written on the subject. https://stackoverflow.com/a/29494337 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.
- haspok 3y ago> 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.
- rogerkeays 3y agoYes, 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.
- haspok 3y agoA 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?
- rogerkeays 3y agoThe 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.
- Etheryte 3y agoThis whole what-if scenario is a complete non-issue. Many other languages do this out of the box and have no issues with it.
- tsimionescu 3y agoThe 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.
- taraparo 3y agoI like the way static extensions are done in Haxe https://haxe.org/manual/lf-static-extension.html 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.
- rogerkeays 3y agoInteresting. 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?
- taraparo 3y agoThe static method can be anywhere afaik.
- twic 3y ago> 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!
- grashalm 3y agoAnother 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.
- 3cats-in-a-coat 3y agoLombok is not a Java fork.
- grashalm 3y agoWell 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.
- rogerkeays 3y agoDo you really think a JEP is worth investing my time in? AFAICT, the Java gods have already made up their minds on extension methods.
- grashalm 3y agoI 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.
- hexmiles 3y agoCan 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.
- rogerkeays 3y agoYou can call a static method on an instance. This project allows you to call static methods not defined within the instance's class.
- mi_lk 3y agoAnd what’s the benefit of that? Except maybe you can chain it and it looks a bit nicer
- jitl 3y agoTab 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!!
- sorokod 3y agoFor 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.
- sacnoradhq 3y agoYep. 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.
- rogerkeays 3y agoFluent 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.
- sacnoradhq 3y agoOh 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?
- rogerkeays 3y agoThe 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.
- jitl 3y agoI 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.
- DeathArrow 3y agoWhy is calling SomeObject.SomeFunc() arguably better than calling SomeFunc(SomeObject)?
- mattmanser 3y agoI'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.
- sacnoradhq 3y agoYou'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/ https://java-design-patterns.com/patterns/fluentinterface/
- rogerkeays 3y agoNice example. The extra parameters really drive the point home. Hope you don't mind if I steal it for the README.
- uguuo_o 3y agoNot to be confused with the Ansys flow solver Fluent: https://www.ansys.com/products/fluids/ansys-fluent https://www.ansys.com/products/fluids/ansys-fluent
- TacticalCoder 3y ago"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/#explanation https://java-design-patterns.com/patterns/fluentinterface/#e...
- rogerkeays 3y agoHow do you do the same thing to java.lang.String?
- ivan_gammel 3y agoA class MyStringBuilder with domain-specific fluent interface can do the job perfectly.
- rogerkeays 3y agoand a MyStringBuilderInterfaceProxyFactory too of course
- ivan_gammel 3y agoHate 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.
- 3y ago
- ivan_gammel 3y agoThis 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.
- latchkey 3y ago.resolve seems like the wrong verb here though. What are you passing into http.get? a url, not something resolved.
- ivan_gammel 3y agoHow would you call a process of determining a correct URL for a request from a base URL and an arbitrary string? This is a quite common name for it, e.g. https://www.npmjs.com/package/resolve-url https://www.npmjs.com/package/resolve-url
- iovrthoughtthis 3y agothus 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
- deleted 3y ago[deleted]
- cutler 3y agoEver noticed how whole industries such as this exist merely to solve the incidental complexities of OOP?
- mdasen 3y agoI 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 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.
- za3faran 3y agoCan you elaborate on what you mean by expression trees for lambdas?
- exabrial 3y agoThis 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_.
- usrusr 3y agoThis 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).
- d_burfoot 3y agoThis 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(...); }