Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I am legit impressed, and I say this as one who has been very hard on string interpolation in other languages; see https://www.jerf.org/iri/post/2942/ and the matching https://pkg.go.dev/github.com/thejerf/strinterp#section-read... for instance. I have criticisms most developers don't even think about.

This isn't exactly what I laid out, of course, but I think it achieves the goals I was looking for, which is the real issue. In particular, having the default string interpolation be prefixed with "STR." is enough for linters and scanners to get a chew on (it's easy by scanner standards to track that a STR.-interpolated string got fed into a database query), and for code reviewers to develop an instinct to look at such interpolations more closely than they need to for a DB. interpolation. An STR annotation is not technically necessary for the former, if it were simply the default, but it is a big deal for the latter. I want people to have a chance to notice and think about their use of bare string interpolation for at least a fraction of a second as they type "STR." (or autocomplete it or whatever).

This does put a heavy burden on the libraries to implement it correctly, but if they do it's even safer than what I was thinking.

One thing though: Please for the love of the internet, for those of you writing interpolators, DO NOT write an interpolator that picks apart the values passed in through a \{ ... } and starts instantiating arbitrary classes via Java reflection. Just stay away from that entirely, OK?



Scala has had all of that for years. This JEP literally copies the design of Scala string interpolators. Except that Scala has macros so, if desired, a string interpolator can perform validation at compile time, making it even more secure than what is being proposed here.


I guess it's an advantage of playing catch-up. You can learn from other languages their experience and mistakes.

Still props for the Java team for doing a good job.


Back in 1997, when James Gosling outlined his vision for Java, he said it should be a conservative language (wrapping a very innovative runtime) that would ideally only adopt features that have proven themselves, for some time, in other languages. Being a last mover is at the very core of Java's evolution strategy. It's not playing catch-up because we're not trying to adopt all features other, more "adventurous", languages have, but rather to selectively pick the fewest features that would have the biggest impact. That's the aspiration, at least.


And honestly, in my opinion, that approach is just really proving itself right now. I am so happy to see Java seemingly on the right path again, adopting solid capabilities, innovating, but letting other languages take some punches first. We have had some dark days (maybe a dark decade), but full steam ahead now. Thank you pron and team!


It is not really innovating, if it has been battle-tested by other languages, is it?

While modern Java sometimes makes me think about reviving and modernizing an old project of mine, if Java is simply always behind by design of its evolution process, that makes it less likely, that I want to spend time with it.


Java innovates a lot in the runtime, where it's ahead of virtually all other languages in its combination of performance and observability. But the language very much tries to be conservative and not to innovate (compared to others, that is) for the simple reason that the vast majority of programmers prefer it that way, and Java is a mass-market language. The language itself is not supposed to excite or to challenge but to inspire confidence that you can build a 100KLOC-10MLOC piece of software in it and your investment would be safe 10, 15, 20 years from now.

This strategy has worked really well for Java, and it's worked really well for those who choose Java. Those companies who 10, 15, 20 years ago picked more exciting languages like PHP and Ruby are generally not as happy with their choices now as those who picked Java.

We fully understand that a minority of developers want more feature-rich, adventurous languages, and we're happy that the platform offers them such choices.


Perhaps it's not fair to blame the language, but the javax -> jakarta EE transition and Oracles license changes have broken a lot of faith in stability that must be frustrating as a language researcher with that goal.


Oracle's "license change" was to open source the entire JDK for the first time in Java's history (although many people tried to make sure this was misunderstood, which wasn't helped by Oracle's poor communication -- now that's frustrating), and we had no control over Jakarta's insistence to leave the JCP, Java's standards body that controls the javax namespace. Even Oracle only uses the java/javax namespaces for JCP APIs. They claimed it was too inefficient, but Java SE churns out two specification versions a year through the JCP, despite them being bigger and more complex than Jakarta EE.


> Oracle's "license change" was to open source the entire JDK for the first time in Java's history

The parent comment is most likely not talking about that. The "Oracles license changes" (plural) in question are things like the recent change to count all employees (even the ones who do not use Java) when calculating the price for an Oracle JDK license (see for instance https://houseofbrick.com/blog/oracle-java-pricing/), or IIRC an earlier change to require a paid license for newer releases of Oracle JDK 8 (which AFAIK is the version most companies use). It's true that it's easy to avoid all of that by exclusively using OpenJDK instead of the Oracle JDK (but then you find out that you have to download from AdoptOpenJDK instead of downloading directly from OpenJDK, and then you find out that it's no longer called AdoptOpenJDK, and now has an even weirder name), but it's also true that it's easy for an employee to end up downloading the Oracle JDK (especially when it's someone who learned Java back when the recommendation was to prefer the Sun JDK instead of an open alternative), which could expose the company to huge licensing costs. It can be less risky to just forbid the use of Java outside of carefully curated environments.

> and we had no control over Jakarta's insistence to leave the JCP, Java's standards body that controls the javax namespace.

It doesn't matter who is at fault; all the users see is a pointless namespace change, which breaks both source and binary compatibility for something which had been part of the JDK for many releases. To make things worse, it's not something which can be easily be worked around; when providing a library which can run with either the old or the new namespace, you have to provide two separately compiled JARs, and I've seen several popular projects take that path. It's very similar to the Python 2 to Python 3 transition, except that Python allows you to dynamically import a module at runtime (so you can easily create a compatibility shim), while with Java, the package names are fixed in the bytecode. The best workaround I've seen so far (though I haven't played with it yet) manipulates the class bytecode at load time to change the package names (https://github.com/eclipse/transformer).


> things like the recent change to count all employees (even the ones who do not use Java) when calculating the price for an Oracle JDK license

AFAIK, the change to the pricing model for support was done at the request of Oracle's support customers, as it's easier for them to track. I don't know why non support customers would care one way or another.

> but then you find out that you have to download from AdoptOpenJDK instead of downloading directly from OpenJDK

The OpenJDK website's downloads link links here: https://jdk.java.net

Of course, you can download any of the other builds if you prefer.

> which could expose the company to huge licensing costs

That's incorrect. The Oracle JDK is free to use.

> all the users see is a pointless namespace change

That's perfectly understandable, and users who are bothered by this are welcome to stop trusting the libraries that have chosen to do that to them. But the Java ecosystem is decentralised and Oracle has no control over third-party libraries. Allowing a library that chooses not to be an official Java standard to pretend that it is is also unfair. The maintainers of that library have decided that it would be better to inflict that change on their users rather than remain a Java standard, and maybe they know what's best for their users.


> That's perfectly understandable, and users who are bothered by this are welcome to stop trusting the libraries that have chosen to do that to them. But the Java ecosystem is decentralised and Oracle has no control over third-party libraries.

The libraries which did the namespace change are things like JAXB and JAX-RS and JAX-WS, which have been part of Java since Java 6. It's not some random third-party library, and Oracle did have control over them, until one day they decided they didn't want them anymore. As a part of Java, users did expect them to keep working (other than small changes like adding or removing a couple of methods, which became even less of a risk of breakage once Java 8 introduced default methods). The use of these libraries is pervasive all over the Java ecosystem, which leads to the complaint mentioned in the comment above: by first removing them on Java 11, and then forcing their new maintainer to rename the packages (instead of grandfathering them as an exception, or even better, providing an aliasing mechanism so that the renaming wouldn't break binary compatibility), Oracle broke some of the faith people had on Java's stability and backwards compatibility.


> and then forcing their new maintainer to rename the packages

Their new maintainers were not forced to rename the package; they chose to do it. That the java/javax namespaces are governed by the JCP (which will turn 25 years old this year), is well-known to anyone familiar with Java's governance, and I can't see a reason why an exception would be warranted if the maintainers had the choice of staying in the JCP; they weren't compelled to leave -- they wanted to leave. Why would a standards body lend its seal of approval to someone who no longer wishes to work in that standards body? "I don't want to follow the rule" is not a good reason to grant an exception from a rule.


we should probably also not blow this "problem" out of proportion. If you actively maintain your things, you can probably handle swapping a few strings out that are garantueed to give buildtime errors. If you dont maintain your application and run on antiquated runtimes? well good news, it keeps totally unchanged...

edit: okay, i guess if you do some funky stuff like reflection or having stuff in various config files, it might not give compiletime errors.. still though, recursive search and you'll find the spots in practically all cases


It's more involved than just "swapping a few strings". You also need to make sure that all the libraries you depend on are on the same side of the divide, that is, you have to "swap a few strings" and update all your dependencies at the same time (and these updates could bring unrelated breaking changes with them). And some of these dependency changes might be unexpected; you might not expect a general-purpose logging library to depend on that, yet one popular logging library has to be upgraded from 1.3.x to 1.4.x when changing the namespace from javax to jakarta.

It's even worse when your software can load plugins written by third parties, and your API allows these plugins to expose servlets or similar. You'd have to not only "swap a few strings" and update the dependencies in your software, but also make all these third parties update all plugins at the same time, with no chance of a gradual transition. It's a large amount of pain, for a completely unnecessary naming change.


well sure, I have been through this myself, but I have exercised some restraint in my dependency chain, and so should everyone else. Dependencies can be wonderful, or they can be liabilities.

nevertheless, I stand by my point, sure, you may have a little bit of a crappy time, but thats it. If you dont maintain your application? well you're every bit as fine as before. If you do maintain it? well then you had better be updating dependencies at some point, and sure, you might have chosen a dependency that breaks compat and such stuff, but then you have this problem ANYWAY, and are probably running old code with at best just irellevant bugs, but more likely various security implications (as the dependencies are probably not trivial, right? and those bugs might be like the insanity going on in a certain popular logging library...)


I guess Jakarta thought that leaving the JCP and changing the name was necessary, but that's something only they can explain.


Your mileage may vary, but I went from liking simple languages like Java, to very expressive, complex ones (Scala, Haskell, Rust) back to Java.

I mean, I like all those languages (and many more), but the thing is, most programs really don’t need all that many language features — an okayish type system, OOP for encapsulating private vs public state, and preferring immutable public APIs have served me well in almost every problem thrown at me.

I would much rather take a “boring” language with an absolutely huge ecosystem and just get my work done. Especially that after going high-level/managed, there is no feature that would give any significant productivity boost (as per Brooks).


Smalltalk should be a no-brainer then. Arguably one of the simplest languages with merely 6 keywords and immense power. The ecosystem is of course a question.

I think I can understand the journey from Java -> other -> Java, because as you learn more about programming, you become more capable of solving problems properly in a simpler language. But there might be a point, when you get fed up again with writing the same old stuff over and over again, and journey on into languages, which allow you to abstract from syntax, making programming more pleasant. And then maybe back again, for type safety or another reason. It is very understandable to move between those preferences over time, or even have an inner conflict about what one wants to use for ones next project.


I've tried to get into Smalltalk many times but I find I always get turned off by the required, clunky, development environments



Yes


I really like Java. Also Go, C, LISP. I find most of the languages I like are very pragmatic. I'm much more interested in getting stuff done than being clever. I used to quite like C++ but it seems like a minefield of features and gotchas these days. I'm very much a fan of the principle of least astonishment. I feel like Java manages that well and not being a kitchen sink language aids in that.


It isn't often I see Java described as simple.


Java is a simple language even if people build gigantic piles of abstractions in it. It doesn't have multiple inheritance, it only has like eight base types, only a few well-worn ways to control flow (while, do while, for, if, switch, throw, return).

It doesn't have operator overloading, or array access overloading. Attributes and methods are instantly discernible by the presence or lack of parentheses. It doesn't have templates or macros.

I don't know many languages that are as simple as Java. If anything Java being so simple syntactically is a major reason why it's so wordy.


It has quite a few keywords, you have primitives and objects and you call methods on those. Compare it to C#, Swift or C++, and it is insanely tiny compared to those.


https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_k... lists 50 keywords for Java.

https://en.cppreference.com/w/cpp/keyword lists 97 keywords for C++.

https://realpython.com/python-keywords/ lists 35 keywords for Python 3.8.

https://en.wikipedia.org/wiki/Smalltalk#Syntax lists 6 keywords for Smalltalk.

Neither one of them I would consider small or simple, but you are correct, that Java has much fewer than C++ and I would definitely agree, that it is the simpler language.


Simpler syntax != simpler semantics. Also syntactic sugar can simplify language comprehension but adds tokens.

Keywords is a poor metric for complexity in any sense other than choice of keywords.

That IMHO doesn't make a language complex in any meaningful way engineering wise.


Do you have a link for the 1997 document? The history is surprisingly difficult to explore for being pretty recent.


Here's a public 1997 document where much of that is said:

https://www.win.tue.nl/~evink/education/avp/pdf/feel-of-java...


Thanks!


Right, and that's why Java introduced checked exceptions in 1.0. A feature which wasn't proven back then and remains unproven now.

Another example being overengineered streams. Parallel streams look really cool as a demo, but have been proven dangerous in their default configuration (they can exhaust the default thread pool).


I, for one, like checked exceptions.


Me too. Error handling is a problem that programming languages still struggle with (witness Golang). But IMO Java does it really well.


Checked exceptions are exact analogs to Result types, but better (auto-unwrap, bubble up, stack traces). It is unfortunately far from flawless (inheritance is not a great combo with it), but that has been part of the language since the beginnings.

Streams are not a language feature, it is only a library. And I honestly don’t find them over-engineered, they really make plenty of logics very readable.


Exceptions have implied control flow which makes them strictly worse than the Result types which are, as their name suggests, just types.

Imagine if some other common types had unrelated features like this baked into them. Want a string? Sorry in my new language all the strings need their own separate thread for some reason.

Actually sorry, I forgot, we're in a Java topic, Java actually did have features baked into types. All of Java's user defined types insisted on living on the heap. Just want to make a pair of integers? Sorry, that's an Object now and so it lives on the heap, whereas just one integer is fine, those are just local stack variables. Did they fix that yet?


Regarding user-defined types, probably what you're looking for is JEP 401: Primitive Classes (Preview) [1]. I believe it's currently being worked on.

[1] https://openjdk.org/jeps/401


>Exceptions have implied control flow which makes them strictly worse than the Result types which are, as their name suggests, just types.

If they're checked exceptions, then the control flow is hardly "implied". If anything, it's explicit: this method potentially throws X, so if X is raised, expect this control flow consequence.


I think the "implied" part is in the callsite ambiguity: a Java method marked with "throws X" can raise X at any callsite in its method body, whereas a Rust function of type `Result<...>` has each `Err` variant marked either directly with a return or with the `?` sugar.


The ideal is probably in the middle. Being able to see where exceptions can be thrown is very helpful if you're manipulating state which can be seen from outside the method, because you need to make sure you leave it in a valid state if an exception is thrown. But in a pure function it's mostly noise.


Checked exceptions are explicit. They are no weirder than doing do-notation in an Either monad, the syntax could be more ergonomic but they are clear as day.


> Exceptions have implied control flow which makes them strictly worse than the Result types which are, as their name suggests, just types.

There’s nothing wrong with implied control flow.

> All of Java's user defined types insisted on living on the heap. Just want to make a pair of integers? Sorry, that's an Object now and so it lives on the heap, whereas just one integer is fine, those are just local stack variables.

That’s not how Java works. The runtime is free to store things wherever it likes, with whatever representation it likes. It needs to follow the semantics of the standard, but whatever gets executed is what the JIT felt like emitting.


Pity that the Streams functions don't support checked exceptions.


It's the only sane way.

Checked exceptions were a fundamental mistake which basically makes it impossible to actually use (non-RuntimeException derived) exceptions reliably for their intended purpose. As soon as you introduce any higher-order programming (e.g. functions that take other functions as parameters) you start to have to wrap e.g. IOException... but that means that calling code can no longer catch that wrapped IOException via a simple "catch (IOException e) { ... }"... it instead has to catch whatever you wrapped that exception in... and manually look inside that Throwable's suppressedExceptions list. And so you end up with stuff like Guava's Throwables utility methods.

Of course, this probably happened because Java basically didn't have much in the way of higher-order constructs at the start, so they didn't notice that they were painting themselves into a corner... If they had a more powerful type system something usable might be possible, but as of Java right now, it just hopelessly broken.

I'd suggest that a fix to the langauge would be to simply change all exceptions to unchecked, but unfortunately that might break a lot of code where the above workaround has already been implemented once consumed libraries switch away from wrapping. (Note that it's only Java-the-language which has checked exceptions. The JVM doesn't and Scala/Kotlin are much the better for it.)

EDIT: The JVM is a marvel of engineering. Java... not so much.


> Of course, this probably happened because Java basically didn't have much in the way of higher-order constructs at the start, so they didn't notice that they were painting themselves into a corner...

The problem is generifying over checked exceptions (i.e. parametric polymorphism over checked exceptions), and there's actually an elegant solution that's been long known -- so the "corner" was well-noticed, as well as the means to get out of it -- but that requires laying some groundwork first which wasn't done because the ground wasn't ready, but it's getting there, so stay tuned.

> The JVM is a marvel of engineering. Java... not so much.

This was James Gosling's stated strategy and one of the secrets of Java's exceptionally-long-lasting success. While many developers like feature-rich, sophisticated languages, the vast majority of them really, really don't (although they are underrepresented on HN, certainly when it comes to language feature discussions). Many of the things people need can be hidden in the runtime, wrapped by a conservative, non-threatening language. Gosling called that "a wolf in sheep's clothing."

See the first half of this talk by Brian Goetz on this very subject: https://youtu.be/Dq2WQuWVrgQ


> The problem is generifying over checked exceptions (i.e. parametric polymorphism over checked exceptions), and there's actually an elegant solution that's been long known -- so the "corner" was well-noticed, as well as the means to get out of it -- but that requires laying some groundwork first which wasn't done because the ground wasn't ready, but it's getting there, so stay tuned.

Happy to hear something might be getting done about it, but users have suffered for 20+ years. I'm probably not going back to Java anyway :).

Any technical details you can share about a potential redesign? AFAICT checked-exception-like-things will always be at odds with the variance requirements on method signatures, so I'm a bit curious. (Outside of the natural solutions where variance isn't a problem, e.g. polymorphic variants or row types.)

Regardless, I'm waaay more interested in the JVM-level improvements that you & co. have been doing! Kudos! Btw, any inside tips on TCO? ;)

> [Gosling]

I have seen that talk, and I largely agree with the philosophy (assuming the goal is correct), but in this particular case, I think it was a mistake to let the problems fester for 20+ years rather than just admitting defeat and removing the checked-ness. If it's going to take that long to implement a solution to a constant pain point... the solution might as well not exist. (Perfect = enemy of good and all that.)

EDIT: Just to add: I definitely don't mean to cast aspersions on anybody in the Java language design team or anything like that -- if that's how my original comment came across. Everybody makes mistakes and predicting the future is hard at the best of times :). I just wish they'd reevaluated some things along the way...


> it's getting there, so stay tuned

It's nice to see from a theoretical perspective. But my impression is that the ecosystem is moving towards having no checked exception. What will happen to the existing code when generics works with union typed checked exceptions?


It's not impossible, it would just require type inferencing/unification on the checked exceptions as part of the function signature.


It runs counter to variance rules for method signatures; see pron's sibling comment and my reply to that. I'm not exactly sure what he's alluding to in terms of an actual solution, but he is an insider, so there may be hope for Java programmers :).

Anyway, the key point is that any implementation of a method will (almost by definition) need to be able to throw more types of errors than the writer of the original interface anticipated. That is the fundamental problem and unless you get rid of overriding there's no solution for that. The only solution is to force everything into a single super-type... but that just leads to "throws Exception" and we've been there before.

(I mean, there's approximations to solutions as in OCaml's polymorphic variants, row types, intersection types, etc. .. but those only work when variance isn't involved... or do away with most of the touted benefits of the "checked" part of checked exceptions, so...)


Yeah, you're mostly right, but then... generics.


I think it’s being worked on and they have an idea/solution.

But Java lives and dies by backwards compatibility so they have to be very careful about it.


Thanks for choosing only tried and proven tech like pervasive nullable pointers.


> One thing though: Please for the love of the internet, for those of you writing interpolators, DO NOT write an interpolator that picks apart the values passed in through a \{ ... } and starts instantiating arbitrary classes via Java reflection. Just stay away from that entirely, OK?

Indeed. Please no more log4shell hell


I think I’ve discussed this on and off with people for almost six years at this point. I’m so glad to see it’s finally happening.

Now we just need to combine this, the constants work that has been done over the last few years, and regular expressions, to make the regexp API so much nicer.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: