Hacker News new | ask | show | jobs
by 66fm472tjy7 1704 days ago
I don't think this is going to happen with Brian Goetz as language architect. He refuses to add many features that would address everyday pain points such as:

* null safe navigation operator

* properties

* mutable records

* a way to ignore checked exceptions (or at least having the stream API take functional interfaces that can throw exceptions)

* adding functional methods like .filter()/.map() directly to collections instead of having to .stream().map(..).collect(toList())

6 comments

He does not refuse, but needs to look at bigger picture.

Some features are low hanging fruit, some are proposed small changes that actually should be defined a bit differently to be better and they will take more effort.

But even small hanging fruits means that they need to chose which ones to do. Should they delay pattern matching by 6 months to get elvis operator? I wouldn't like it, pattern matching is more important.

Basically they have limited resources and have to chose what to work on.

Thank god for that, because those feature are either horrible on their own or superseded by better ones. You're getting a car and complaining for not getting faster horses. The features Java has adopted -- specifically, algebraic data types and pattern-matching -- will lead to better development practices, rather than making it easier to work with inferior ones.
> Thank god for that, because those feature are either horrible on their own or superseded by better ones.

Are they though? I agree mutable data classes and property _setters_ are bad, but what about the rest?

I personally like the idea of checked exceptions, but there is no doubt they failed to deliver on Java. Perhaps the good parts of this idea can be salvaged by having an easy way to explicitly wrap a checked exception, like with_context() in the anyhow Rust crate. Kotlin dumped checked exceptions in favor of Results, and I don't see how Java has anything right now that supersedes that.

Explicit nullability is unarguably a good feature. You could make a convincing argument that expressing it with an Option/Maybe is better than the road that Kotlin, Swift, C# and TypeScript took. But Java's Optional<T> is not that solution, since the T within the Optional can still be null. And I'm not even getting into how verbose and unnatural using it feels, or the small issue of having to rewrite all the existing Java APIs to make it work.

The same goes for having eager collection operators. The JIT compiler might be able to inline and optimize simple stream operators, but the ergonomics of doing simple things on a collection is just bad.

But even if Java does all of that, just lack of extension methods and top-level functions make using Java a nightmare for me. If Java had extensions methods (or an equivalent concept like Type classes or the Uniform Function Call Syntax that D has), the previous complaint about lacking collection operators directly defined on Collection<T> and Iterable<T> will be a non-issue. Just let anybody who wants it define their own.

> I personally like the idea of checked exceptions, but there is no doubt they failed to deliver on Java.

Why is there no doubt? I agree that their interaction with lambdas leaves much to be desired, but those problems can be fixed.

> Explicit nullability is unarguably a good feature.

It is. It wasn't on the list, which asked for a "null safe navigation operator"; that is not a good feature, as it makes it even harder to see where the nulls are.

> But Java's Optional<T> is not that solution

I didn't say it was. Nullability types for Java are being studied.

> But even if Java does all of that, just lack of extension methods and top-level functions make using Java a nightmare for me.

Extension methods make a language a nightmare for me, though. Java doesn't try to appeal to all people, and no language ever will. It just tries to appeal to most. As for top-level functions -- stay tuned.

  those feature are either horrible
I speculate that most users of languages that have these features (Kotlin has all of them) would be unhappy if you took them away.

  algebraic data types and pattern-matching -- will lead to better development practices, rather than making it easier to work with inferior ones.
I am not arguing against those features, but the ones I asked for seem easier to implement by comparison as they are already in other successful JVM languages. Furthermore, they are far more frequently useful for my use case of web services storing, transforming and moving data around, often without caring too much about their semantics. I speculate that a large portion -probably a majority- of JEE/Spring devs are in the same position.

I am disturbed by how universally true you seem to consider your positions - as if there were no cases in which mutability is preferable to immutability (even if that is sometimes just due to the way some ORM or serialization library works).

If these features are such bad ideas there should be plenty of stories of Kotlin/C# devs cursing the language for providing them with these footguns.

Having a JDK dev respond like this only strengthens my argument that cageface has no reason to fear that Kotlin will lose steam - Java has different priorities. There is nothing wrong with that, but more developers who want these features should be aware that they will not be getting them from future Java versions.

Adding syntactic sugar for properties to the language will cement the worse than ideal get/set naming convention to the language forever, while there are sure better abstractions that could be made instead (eg. records with withers)

Mutable records, especially primitives are a pain in the ass for C#, so it is a bullet avoided. Immutable objects are much easier to optimize away, and are safer to use in concurrent code.

> I speculate that most users of languages that have these features (Kotlin has all of them) would be unhappy if you took them away.

Kotlin has very little impact over the ecosystem due to its small market share (compared to Java). Plus, it has no influence over the platform, or any of the other platforms it targets for that matter, with the possible exception of Android, although I guess that's up to Google. The best it can do on the Java platform is make current practices a little easier. Java, on the other hand, has the option of changing the platform and the ecosystem and moving it toward better practices.

> in other successful JVM languages

Just so that we're clear, for over a decade now, the portion of Java platform developers using all other "successful JVM languages" combined has been pretty much steady at 10%. That's not to say they're not popular in absolute terms, but Java is so stupendously popular, that it's in a completely different league.

> If these features are such bad ideas there should be plenty of stories of Kotlin/C# devs cursing the language for providing them with these footguns.

Those languages appeal to different people, and, in the case of Kotlin, at least, they're the best they can do given their clout. If I don't like something, there will be those who do. The question is, how many? When I said that the features are bad or superseded by better ones, I didn't mean they're necessarily bad for all people or all languages, just that they're either bad for Java or that Java can do better.

> has no reason to fear that Kotlin will lose steam

The way this works is that the alternative languages on the Java platform fight for market share with each other. Kotlin will not disappear because of Java, but it will eventually lose its market to some other alternative language, just as Scala has lost to Kotlin. 10% of people just prefer other languages, and I am very happy that the platform can accommodate them. We'll continue working to make sure that the platform is a good target for different languages that appeal to different people. Other languages don't take away from the Java language, but make the platform more appealing to people who prefer less mainstream languages, and so we all win. Java's 9/1 ratio of Java language vs. others is a pretty good one, and we're trying to preserve it (we would be even happier with 85/15 or 8/2, as it probably means a larger market share for the platform overall rather than a smaller market share for the language. Remember that OpenJDK is funded by people buying support; they're not buying support for the language, but for the platform). If the non-Java-language market has shifted from Groovy and Scala to Kotlin, as that's what most of those in that group prefer -- so be it (although I do wish more people use Clojure; it is, IMO, the most interesting and best design alternative Java platform language out there, and one of the most interesting and best designed languages around in general).

The strategy that keeps the VM is innovative and the language is conservative has worked very well for Java, and because it's expected to remain very popular for a couple of more decades at least, we are very careful about adding features. The goal is to avoid new features as much as possible, and the way to do that is to add fewer, but more powerful features.

I can understand the hate behind null-safe operators, but why properties? Why is bad to have an ability to "upgrade" a member variable into a property, without breaking any existing contracts? I really hate that I need to resort to using setters and getters in Java because those are bad for searchability.
Properties are probably the worst feature on that list, and no experienced language designer would add them to Java. They make it easier to work with setters, while the goal is to reduce their usage altogether! Records achieve that much better. Unencapsulated component access is standardised on classes with good contracts (construction and deconstruction are duals for records) and will work well with patterns, all while avoiding unnecessary mutation for such classes (instead, we'll have "withers" or "reconstructors": https://github.com/openjdk/amber-docs/blob/master/eg-drafts/...).

So we're killing two birds with one stone: making it easier to work with "simple data" while also reducing reliance on getters and setters. On the other hand, C# finds itself needing to add more and more capabilities to this feature, while Java dodged that bullet.

> instead, we'll have "withers" or "reconstructors

When?

> On the other hand, C# finds itself needing to add more and more capabilities to this feature, while Java dodged that bullet.

Java "dodged it" by never even having to fight. While C#'s "poor choices" are leading to a language with less and less boilerplate with almost each year, in Java we'll have to wait another 10 years before it delivers half of a half of the "withers" functionality that doesn't work with half of existing standard library code (the way arrays, lists and collections are three non-intersecting entities).

But at the other hand, C# is heading in the direction of C++, where even after n years working with it, it can still surprise you/find yet another way of doing this and the rest.

Like, I would prefer not having 10+ initializers from C++ in a language.

But on the other hand you have object initializers directly in the language

In Java you have one of these:

- a hope that someone provided a useful static `.of` method

- a hope that someone provided the 15000 setter methods that you have to tediously invoke one by one

- a hope that someone provided a generator that created a builder that has 15000 setter methods that you have to tediously invoke one by one

Meanwhile in C#:

   new Object {
     x = ...,
     y = ...,
     z = ...
   }

for literally any object (provided properties are public of course).

And don't get me started on array vs list vs collections none of which are compatible with each other. And hundreds of other QoL improvements that are begging to be implemented, and are not.

Thanks for the thorough explanation. Now I understand where Java is heading for. The aesthetics regarding withers concerns me a bit though, because I don't like prepending anything to the name of an acceessor (a transformer, in this case?), which was why I liked the idea of properties in the first place.

I hope both Java and Kotlin can converge into a common coding style. As you said regarding C#, Kotlin's emphasis on properties makes it easier to use getters and setters and thus may hinder getting rid of them in the Java ecosystem. I'm not sure if I should support Kotlin's success at this point.

> will lead to better development practices, rather than making it easier to work with inferior ones.

C# has these "inferior practices" and is a much better language overall. Why Java doesn't adopt the low and not-so-low hanging fruit from C# is really baffling.

Anything from yes, properties, to object initialization shortcuts and a sane IEnumerable/Collection interface that doesn't require stream/toList everywhere.

> C# has these "inferior practices" and is a much better language overall. Why Java doesn't adopt the low and not-so-low hanging fruit from C# is really baffling.

Because Java is doing a lot better than C#, and obviously we feel Java is a much better language overall.

Java has had quite a head start (.net core came out only 5 years ago).

Now the "doing a lot better" is dwindling. And dragging feet on QoL isn't helping.

The gap between Java and C# has grown very slowly but surely for many years, and more people feel Java is changing too quickly than too slowly. Different people prefer different things, some will always prefer Pepsi (although C# will probably not be that Pepsi, as it's lost too much ground and MS seems to be losing interest in it, as they tend to do), and our goal isn't to adopt the strategy of less successful products, but to forge our own, even if some on HN have other ideas.
> it's lost too much ground and MS seems to be losing interest in it

wat

> our goal isn't to adopt the strategy of less successful products

This sounds like "we don't care if some other languages have great quality of life improvements, we pretend that success (for some unknown definition of success) is the only thing that matters, so we'll keep implementing one or two features every 10 years that don't work with half the language and standard lib, and still require hundreds of lines of boilerplate to work with."

In a sibling comment you're pretending that withers is a thing that will ever happen. It won't, not for another ten years. Meanwhile we're left with a language that has to resort things like Lombok to try and cut down on the ridiculous amounts of code one still has to write for even the simplest things. Or almost but not quite incompatible lists and collections (none of which have even a pretence of a DX for easy construction). Or value types that have been a proposal for 9 years, and generics with primitive types that has been a proposal for 7. And the list (or collection? or stream? or?...) just goes on and on and on.

Thank god we finally got multiline text blocks and some form of pattern matching. Wait. Those come from less successful languages. How could you?

Question from a non Java programmer, where can I read more about algebraic data types in Java? The article doesn't mention the term.
They are not used that much yet, and the whole deal (with destructuring pattern matching) is not yet ready, but it is basically just records as (nominal) product types and sealed classes as sum types.

What is already possible with switch expressions is branching based on type, in a much more ML-like style like case Point p -> … which doesn’t require instanceof checks and castings, but it will soon become Point(x, y), where the x, y will automatically get the values of the corresponding fields.

Stream has been given a `toList()` convenience method:

https://docs.oracle.com/en/java/javase/17/docs/api/java.base...()

Is that true though? nullability is a area I've heard /u/pron mention may be tackled in the future, mutable records seem like they will be tackled with the 'withers' concept in the future. Checked exceptions + changing the sugar of .stream().map I don't see ever changing. Properties I am unsure of.
Why should it be in the future. The syntax of .stream() has been awfully verbose since Java 8: in the middle of “.stream()….collect(toList())”, you need to squint to find the actual function name being applied between the flood of polluting calls, which loses the appeal of functional programming.

In my company we have f(list).map(…), which is a wrapper for the streams. But newcomers don’t like it because it’s not the standard.

AFAIK, the fate of nullability is tied pretty directly to Valhalla. It's constantly being brought up there.
IMHO safe navigation only makes sense when the compiler is able to reason about nullability, meaning nullability is rooted in the type system. That's the case for Kotlin and not for Java.

Otherwise, you've got a high risk of developers inserting a ? to fix a bug, which only fixes the symptoms.

As of now, Optional#map seems to be the way to go.

Re: Properties: What is the gain? What is the "pain point"? More typing? Can you share a language where you feel properties is a significant gain? (C#?)

Re: Streams API that allows checked exceptions. Brian Goetz has written about this issue. (Search StackOverflow.com) They did not allow because of the optionally parallel (threaded) nature of streams. If multiple parallel streams throw an exception, when/when/where/how are the exceptions re-thrown? Moving exceptions across thread boundaries is a tricky thing in any language. If you don't care about parallel streams, there are many open source projects that effective create a nearly identical Streams API but allow throws Exception everywhere. Are these open source solutions insufficient for your needs?

Re: adding functional methods like... Is there an advantage other than typing nine less characters?

> Moving exceptions across thread boundaries is a tricky thing in any language.

Not really. C++ has std::exception_ptr for that exact reason, and C# has ExceptionDispatchInfo. Both are very straightforward - you catch the exception, get a handle for it, and then re-throw that handle in another context, with original stack trace preserved.

Multiple concurrent exceptions is also a solved problem. In C#, if you do something like Task.WhenAll(), and one or more task throws, you get back an AggregateException, which can be inspected to see which task threw what. The same can be applied to async streams.