Live data from Hacker News

Retrofitting null-safety onto Java at Meta

engineering.fb.com

201–210 of 230 posts

Re: Retrofitting null-safety onto Java at Meta

#201

Earlier quoted context omitted.

It sure can be a problem when people initialize stuff with nonsense to avoid running into nullable warnings. However, accidentally running into null pointer exceptions is still worse than people deliberately footgunning themselves with bad workarounds. I feel C#'s nullable has helped me personally to avoid a lot of potential bugs and also changed the way I write code in a lot of places - like creating `bool Try...(..…

I think you have to separate nullable types from the global nullable directive. The global nullable directive will just make people return nonsense like the trend some years ago when people started to return empty lists instead of null.

"Uncle Bobs" disagree.

Re: Retrofitting null-safety onto Java at Meta

#202
post #40

Earlier quoted context omitted.

Don't people use @NonNull everywhere now? It's been a few years since I've programmed in Java but even then I feel like that was common practice.

“Unannotated types are considered not-nullable” Defaulting to not-nullable is a great idea. Much less boilerplate.

Rather than opting into a slightly incompatible dialect for some but not all code, I would like an IDE that lets me specify what is @Nullable, and quietly inserts @NotNull everywhere else without displaying it. We can keep the boilerplate in the bytecode without rubbing our noses in it.

Re: Retrofitting null-safety onto Java at Meta

#203
post #125

Earlier quoted context omitted.

The guys from Scala.NET did mention .NET generics as one of the reasons they gave up on porting Scala to .NET. That is also a reason why .NET needed the whole effort to create DLR and the dynamic keyword, while Java only needed to add one additional bytecode invokedynamic.

.NET has F# and Clojure so not convinced by this

It only reveals how little you know about type system differences between Scala, F# and Clojure.

Re: Retrofitting null-safety onto Java at Meta

#204

Earlier quoted context omitted.

It's okay for null to be a part of the language as long as the type system can reason about it (via nullable-types, sum types, etc). Dart, TypeScript, Kotlin are examples. But yeah, Go is the problem-child that doesn't have any way of statically reasoning about nulls

Yes, it's OK to have null in the language in limited cases. I'm fine with null so long as the language enforces checking for null when it could cause a NullPointerException (or that language's equivalent). Java's biggest failure in null handling was allowing EVERY reference type to be null without a way of specifying that it could be non-null. This basically ensured that null could creep in almost anywhere. Allowing…

TBF what Java does was pretty standard back in the 90s when it was released. Though obviously it would be nice if this had been fixed since. Not that there were no grumblings and better alternative but it was very much the little (if at all) questioned norm for procedural langages.

It’s even excusable for C# in the early aughts.

Re: Retrofitting null-safety onto Java at Meta

#205
post #106

Earlier quoted context omitted.

It's like when people say Java had the chance to do generics right (like C#) and then didn't Yeah technically tomorrow morning Java could fix it, but there have been kingdoms built on the current situation. C# took its lumps years back on breaking things and so there were fewer kingdoms to demolish. And if you fix it, it'll be years before it trickles down to a large portion of devs who don't get to work at the bleed…

First of all, Java's generics are already superior to C#'s. We've exchanged the minor inconvenience of not being able to have overloads that erase to the same type with the ability to support multiple variance strategies rather than one that's baked into the runtime. That's why Java has Clojure and Kotlin and Scala and Ruby running on top of it with such great interop. And, as it turns out, when specialisation is rea…

> First of all, Java's generics are already superior to C#'s.

That's... highly controversial to say the least. You didn't just lose the ability to overload List and List. You've also lost run-time type information and type-safety when doing reflection. And reflection-based metaprogramming cannot be ignored in Java (and to a lesser degree in Kotlin): So much is based on it!

It also makes interfacing with primitives (especially arrays of primitives) quite messy. And you've also lost some possibilities for JIT optimizations, but I'm not sure if C# does them anyway and how much they really matter - so that part may be minor indeed.

Allowing different variance strategies could be a boon for a language-agonstic runtime, but, as other comments mentioned, that's just rewriting history. The JVM wasn't built to be a language-agnostic runtime. The truth thoroughly documented. It started with a new Java-like language, Pizza, that had to run on the then-current JVM, and continued with a fork of Java called Generic Java. These attempts were independent at first, so they could not change the JVM. Type erasure was chosen because it was the best fit to the existing Java model and had the best performance with the JVM as-it-was[1].

In hindsight it turned out to make writing JVM languages with different variance strategies easier, but it's not a superior design, just a coincidence. Although Martin Odersky was behind both Pizza and Scala, he did not mention variance as concern back in the 1990s when he chose type erasure. Let's not try to rationalize a JVM-compatibility decision that was done by a team outside Sun as some great insight by "the brilliant engineers at Sun".

In the end of the day, you can do type erasure just as well on .Net when you need it, the same way you'd do it in Java. Nothing prevents a CLR language from compiling List as List.

> We've also added virtual threads years after C# had async/await, and now they're the ones stuck with that inferior solution, which we'll remain in the ecosystem even if and when they add user-mode threads.

Virtual threads are not inherently superior or inferior to the async/await mode. It's the old stackful coroutine vs. stackless coroutine or implicit-vs-explicit concurrency debate. I've already written about it multiple times in the past[2]:

Stackful pros:

* No "colored functions": I can write asynchronous I/O code the same way I write multi-threaded code or non-blocking code. It's definitely more ergonomic, but also means your code can hold more surprises for you. Not everybody thinks 'Colored Functions' are an absolute advantage, otherwise we wouldn't have Effect Typing.

* Less GC allocations: stack is [re-]allocated in bulk every time time a lightweight starts (go statement) or whenever it needs to grow. Stackless coroutines typically allocate a smaller object on every call to an async function.

Stackless pros:

* Clear yield points: you always know the points where your coroutine could suspend and control would be moved to another coroutine: whenever you see an await statement. This is why function colors are needed - to be explicit about the control flow.

* Less memory waste and less overall allocation size: the "compile-time stacks" generated by stackless coroutines are perfectly efficient. There is no unnecessary memory allocated.

I personally have a strong preference for explicitly marking concurrency, just like I would love to distinguish pure function and functions that have outside effects: I think Effect Typing is generally a good thing. Java also went its own way with explicit and mandatory effect marking when it comes to checked exceptions. Unfortunately, it never really got composability right, and using checked exceptions is simply too verbose - but Rust also went for explicit error type signatures, and its solution is generally well-received.

[1]: https://dl.acm.org/doi/abs/10.5555/647373.724066

[2]: https://news.ycombinator.com/item?id=24361590

Re: Retrofitting null-safety onto Java at Meta

#206
post #198
post #189

Earlier quoted context omitted.

Java's mid 2000 peak was anomalous, not just for Java, but for any language. I can't think of a language (maybe C in the 80s?) ever dominating so much of the market. The market is now much more fragmented. What you should be asking is, which language is doing better? JS and Python are the only candidates. No single language is currently posed to take its place (although PHP and Ruby came closest), and no language out…

Several languages had similar levels of dominance. Fortran was even more dominant in the late 1960’s, then C in the early 90’s followed by Java in the 2000’s all had clear dominance. It’s possible though harder to verify if Pascal reached that level and we might see Python hitting it soon.

> Several languages had similar levels of dominance.

I don't think Fortran was ever quite that dominant, but we can certainly agree that no language has been as dominant since Java.

> It’s possible though harder to verify if Pascal reached that level and we might see Python hitting it soon.

Not even close for either one of these. I was already programming in Pascal's heyday, and it was mostly used in education. It was never very popular in industry. Python is extremely popular, but many of its users are not professional programmers, and it isn't dominant in big server software at all. In the early 2000s, Java dominated servers, clients, and education. I don't think any language is even remotely approaching that today, but JS would be the closest (although still very far).

Java today is the #1 most popular server-side language, certainly for big software, and by a big margin. According to the best data we have [1][2], it's about 1.5x-2x more popular than C#, maintaining the same gap those two languages have had for about 15 years. It's about 7-15x more popular than Go.

So other JS and Python, all other languages's are doing worse than Java, with prospects that look significantly worse than Java's. No language is even coming close to threatening Java's position as PHP and Ruby were. Node.JS looked like it could have for a while, but then it sank quickly; some thought Go might do it, but while it's certainly interesting and there's much we can learn from it, its growth has stalled.

Comparing Java to itself 20 years ago is unfair, because it was an unusual time for programming. But when you compare it to the competition today, you see that Java is doing spectacularly. I would like to see it taught more in schools, though, where Python has overtaken it.

[1]: https://www.devjobsscanner.com/blog/top-8-most-demanded-lang...

[2]: https://www.hiringlab.org/2019/11/19/todays-top-tech-skills/

Re: Retrofitting null-safety onto Java at Meta

#207
post #176

Earlier quoted context omitted.

> First of all, Java's generics are already superior to C#'s. We've exchanged the minor inconvenience of not being able to have overloads that erase to the same type with the ability to support multiple variance strategies rather than one that's baked into the runtime. Working on the JDK I'm sure you're aware just how revisionist that take is: we got the version of generics that landed because of backwards compatibil…

> we got the version of generics that landed because of backwards compatibility concerns. We got the version of generics because of the need for two languages with different variance strategies -- Java 1.4 and Java 5 -- to be compatible, and so precluded baking a particular variance strategy into the runtime. It is true that the goal at the time wasn't to support, say, Scala or Clojure or Ruby specifically, but why d…

> Erasure, BTW, is a pretty standard strategy. Even Haskell does not distinguish at runtime between List Int and List String.

Haskell doesn't do overloading and reflection, so for Haskell type erasure is a pure implementation detail. It doesn't leak out to the programmer.

Re: Retrofitting null-safety onto Java at Meta

#208
post #109

Earlier quoted context omitted.

Can you point a couple of cases where Java got a better version? Generics? Value Types? Lambdas? Null Safety? Async/Await? Also do you just have to assign all the value the features provide in interim 0 value? Like Async/Await has provided a 10 years of value, and I'm guessing you mean Project Loom... so how much better than Async/Await does Loom have to be to justify an entire decade of just straight up missing the…

I can! Generics, virtual threads, and records. Java is unburdened with the prevalent and obsolete features of async/await, properties, and its generics allow a flourishing ecosystem in exchange for a minor inconvenience.

> Java is unburdened with the prevalent and obsolete features of async/await, properties, and its generics allow a flourishing ecosystem in exchange for a minor inconvenience.

I don't quite get the "unburdened" part. C# now has records as well[1], and it always had List. It may also get stackful coroutines as an alternative to stackless coroutine.

You could say that C# now has to deal with a lot of legacy code that's using properties and async/await and cannot be replaced with "superior" records and stackful coroutines[2]. That's all true, but Java hasn't been frozen in vacuum for the last 20 and 10 years (respectively).

While C# programmers created mountains of legacy code with properties, Java developers churned out mountains of legacy code using "Beans" with getters and setters, auto-generated by the IDE or Lombok. That's strictly worse.

At the same time during the last 10 years, Java programmers that needed Async I/O didn't just sit around and wait for Project Loom. They wrote callback hells, and then moved on to CompletableFuture or a reactive programming style. And all this code tends to be a lot messier than Async/Await.

In short, real world Java developers are just as burdened as C# developers by legacy solutions that predate virtual threads and records, and the Java legacy solutions are usually quite a lot uglier.

[1] Although I dislike their decision of permitting mutability, their records they are at least useful right now, since we don't have to wait another 5 years to get a with expressions.

[2] I completely agree with you that immutable equality-based types are superior, and that mutable data types probably shouldn't be baked in the language. But I disagree with the stance that stackful coroutines are superior.

Re: Retrofitting null-safety onto Java at Meta

#209
post #106

Earlier quoted context omitted.

First of all, Java's generics are already superior to C#'s. We've exchanged the minor inconvenience of not being able to have overloads that erase to the same type with the ability to support multiple variance strategies rather than one that's baked into the runtime. That's why Java has Clojure and Kotlin and Scala and Ruby running on top of it with such great interop. And, as it turns out, when specialisation is rea…

> First of all, Java's generics are already superior to C#'s. That's... highly controversial to say the least. You didn't just lose the ability to overload List and List . You've also lost run-time type information and type-safety when doing reflection. And reflection-based metaprogramming cannot be ignored in Java (and to a lesser degree in Kotlin): So much is based on it! It also makes interfacing with primitives (…

> And reflection-based metaprogramming cannot be ignored in Java (and to a lesser degree in Kotlin): So much is based on it!

Its prevalence has been going down, and we're trying to encourage that trend.

> Allowing different variance strategies could be a boon for a language-agonstic runtime, but, as other comments mentioned, that's just rewriting history.

I don't see how this matters, and I never claimed that supporting, say, Clojure was the intent, but the design was very specifically to allow for multiple variance strategies (because Java 1.4 and Java 5 were two languages with different variance strategies).

> Let's not try to rationalize a JVM-compatibility decision that was done by a team outside Sun as some great insight by "the brilliant engineers at Sun".

I don't think I was doing that, but on the other hand let's not discount three decades and hundreds of decisions that have made and kept Java one of the biggest and longest-lived successes in software history. Call it lucky if you want, but they/we have been consistently lucky for three decades now. During that time, MS have made huge breaking changes to their platform at least three times.

> Nothing prevents a CLR language from compiling List as List.

And Java could specialise just as Ceylon did. Erasure ended up working so well for Java because the main language on the platform did it. It is that, not some hypothetical ability, that was able to support Clojure and Kotlin well.

> Virtual threads are not inherently superior or inferior to the async/await mode. It's the old stackful coroutine vs. stackless coroutine or implicit-vs-explicit concurrency debate.

Actually, it isn't. User-mode threads might not be superior to syntactic stalkless coroutines in every language and on every platform (and certainly not for every use-case), but they are in Java (and could be in .NET). For one, we are now close to achieving zero memory waste in virtual threads compared to async (will be released in a year or so); in fact, we already allocate fewer objects than Kotlin's stalkless coroutines [1]. For another, while it is true that the less composable cooperative model (i.e. "clear scheduling points") has some advantages in languages like JS (where a non-cooperative model would break pretty much all existing code), Java and C# already have the non-cooperative model built into the language. It's a question of having two semantically similar yet syntactically incompatible concurrency models or just one.

> Not everybody thinks 'Colored Functions' are an absolute advantage, otherwise we wouldn't have Effect Typing.

I'm not a big believer in effect systems in general, for the simple reason that they've yet to show significant tangible bottom-line benefits (although we'll wait and see). But they're clearly wrong for coroutines in languages that already allow side-effects, like Java. The semantics -- in the precise Hoare-triplet sense -- are identical to regular mutation, and they're less composable. So they introduce an additional incompatible syntactic world with identical semantics, which makes them, at best, a redundant overhead.

> Unfortunately, it never really got composability right, and using checked exceptions is simply too verbose

You are absolutely correct there, but I believe we can fix that, and quite elegantly.

Now, I don't want to give the impression that I think Java is a perfect language or platform; I don't. Mistakes were made along the way, especially early on. But I do think we've managed to do better than others. Python is as popular as Java or perhaps even more so, but it didn't manage to preserve compatibility; .NET never achieved Java's popularity while also failing to preserve compatibility (and several times). The only other languages with a similar backward compatibility story alongside long-lived super-popularity are C and JS, and both of which are famous for having a small or nonexistent standard library. So things could have hypothetically been better, but to date no one has actually managed to do that better, or even as well.

[1]: The perfect memory efficiency of stalkless coroutines is only achievable when you disallow virtual calls and recursion -- as Rust does, although it pays for that dearly -- but neither C# nor Kotlin can or want to do that.

Re: Retrofitting null-safety onto Java at Meta

#210
post #176

Earlier quoted context omitted.

> we got the version of generics that landed because of backwards compatibility concerns. We got the version of generics because of the need for two languages with different variance strategies -- Java 1.4 and Java 5 -- to be compatible, and so precluded baking a particular variance strategy into the runtime. It is true that the goal at the time wasn't to support, say, Scala or Clojure or Ruby specifically, but why d…

> Erasure, BTW, is a pretty standard strategy. Even Haskell does not distinguish at runtime between List Int and List String. Haskell doesn't do overloading and reflection, so for Haskell type erasure is a pure implementation detail. It doesn't leak out to the programmer.

Yes, which is why in Java the cost isn't free but it's still rather low. Of course, the calculus has changed due to memory hierarchies, which is why Valhalla brings with it specialisation for value types (that outweighs the benefits of erasure for those types, but they're not variant, so it doesn't require baking a variance model into the VM).
Post reply on HN