Live data from Hacker News

Retrofitting null-safety onto Java at Meta

engineering.fb.com

171–180 of 230 posts

Re: Retrofitting null-safety onto Java at Meta

#172

Earlier quoted context omitted.

> At the same time, we have adopted Kotlin as well. Interestingly, most of our developers prefer to stay on Java... not because they love Java, but because of familiarity and that they seem to think it's "good enough". I have been using Kotlin on Android for a few years now, and really enjoy the extra expressiveness and conciseness that the language offers ... but have come to the conclusion its much easier to write…

In your first example, thats poorly written code that should never make it past code review. I'm also having a hard time thinking of a case where run would even be useful in that situation that wouldn't also fix your second example. If you need all aaa through ddd to be non-null you return early or you check ahead of time aaa ?: return bbb ?: return ccc ?: return ddd ?: return or if (aaa != null && [the rest] != null…

And

   val aaa = aaa ?: return 
   val bbb = bbb ?: return 
   val ccc = ccc ?: return 
   val ddd = ddd ?: return 
if those three-letter friends happen to be "getter vals" that might change behind your back (the compiler is perfectly aware of the difference).

That's the one gripe I have with kotlin, those "getter vals" that aren't even remotely related to immutability. Likely a consequence of building closely on top of java, there's simply no way an interface could promise to never return two different references from a call on the same object without that promise being a lie. And as usual, kotlin provides gentle damage mitigation on syntax level: "val x = x", as in the four lines above.

I would have preferred something even more consise like "val=x" though, where the name overlay is promoted to explicit intention by not typing it twice, as in "call that getter and nail its return to the getter's name for the remainder of our scope". People tend to be reluctant with that name hiding, unsure of it's good style or bad, if there was an explicit shortcut it would be blessed as The Kotlin Way (and, as you said, it's so much better than getting pulled into that let/also nesting abyss that feels clever while writing but turns on you before you are even done)

Re: Retrofitting null-safety onto Java at Meta

#173
post #45

We've been using `@Nullable` as Meta does in our code base for many years now (everything else is assumed to be non-null). Initially, we had CheckerFramework actually doing the checks at compile time... but eventually had to remove it because it's unstable, slow, has not kept up with Java evolution (we're on JDK 17, I think Checker still barely works on JDK 11) and also, because our IDE can be configured to flag erro…

> At the same time, we have adopted Kotlin as well. Interestingly, most of our developers prefer to stay on Java... not because they love Java, but because of familiarity and that they seem to think it's "good enough". I have been using Kotlin on Android for a few years now, and really enjoy the extra expressiveness and conciseness that the language offers ... but have come to the conclusion its much easier to write…

My workplace is trying to get onto the KMM train and I get sweaty palms trying to imagine people onboarding onto Kotlin from other languages.

I mean I love Kotlin, but as you mention there is just so much syntax. The simplest things can be done 100 different ways and it's hard to say what's idiomatic.

I'm personally going to be ruthless about "syntax simplicity" in code reviews. Otherwise I'm imagining a situation where the dream of shared code really just becomes "Android devs throwing a black-box library over the wall at iOS devs" because only they spend enough time with Kotlin to understand it

Re: Retrofitting null-safety onto Java at Meta

#174

I have been writing Java for money for more than ten years and never ever have I had non-trivial problems with null. Less than one percent of the bugs I fixed were caused by nullpointers, less than one percent of write-deploy-test loops were caused by it. I either have code that can't be null (e.g. getters of lists that create a list if the field is null, outright validation before usage), code where null has a desir…

The slow-down doesn't come from NPEs occurring as bugs.

The slow-down occurs from all the extra thinking that happens once [your teammate] allows nulls into the code "as a feature".

If you assume no nulls, then you get to lean on the type system. A String is actually a String, not an instruction to GOTO the nearest enclosing RuntimeException handler.

And honestly, I'd be mostly happy to work on the no-null assumption, hit a few NPEs, and fix them as I go. But someone else will decide that nulls are OK as values, and now every value is suspect. Which means you can no longer just blindly assert against nulls.

And that's the value-add of Optional. You can use it to deliberately represent things that aren't there, meaning you can go back to treating every null as a bug to squish, rather than a design choice.

Re: Retrofitting null-safety onto Java at Meta

#175
post #43

Java has so many deficiencies in its design that so many frameworks are invented to cover its flaws. Just give a real Optional type at a language level. It’s clearly possible in other JVM languages.

> Java has so many deficiencies in its design that so many frameworks are invented to cover its flaws.

Java the language has its flaws, but the frameworks make the situation worse.

> Just give a real Optional type at a language level. It’s clearly possible in other JVM languages.

It's not the presence of Optional that we need, it's the absence of nulls. And nulls are in all the libraries. And you also have to convince your fellow Java developers that null is bad.

Re: Retrofitting null-safety onto Java at Meta

#176
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. 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 does it matter?

> forced other languages to then go and reinvent the exact same wheel in different ways.

I don't think so. Both untyped languages like Clojure and Ruby, as well as existing typed languages such as Haskell, have been ported to the Java platform. Erasure, BTW, is a pretty standard strategy. Even Haskell does not distinguish at runtime between List Int and List String.

> Nullability specifically is much more subject to the part you left out: "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 bleeding edge, or even near the edge."

So what? Java is likely to be one of the world's most popular languages 20 years from now. Who would care if we wait to do things right, especially as it worked very well for Java so far. Who cares now that lambdas were in Java 8 and not Java 5?

As for C# vs Java, there's no doubt some developers prefer one over the other -- for some it's Java, for others C# -- but I see absolutely no reason for Java to adopt C#'s strategy. Even if you don't think it's any worse, it's certainly hasn't proven to be better. Those who prefer C#'s approach already use either it or Kotlin, so we've got them covered on the Java platform, too.

Re: Retrofitting null-safety onto Java at Meta

#177

Or use Kotlin, it's near perfectly interoperable with Java and the acclamation period is measured in weeks if you're a Java dev

I can't think of any popular language that would take more than a few days to get acclimated to as an experienced developer, so that's not a very compelling argument.

C++? Java? Rust?

Re: Retrofitting null-safety onto Java at Meta

#178
post #16
post #5

Earlier quoted context omitted.

As they point out, they already have some Kotlin and use it interoperably, but "rewrite existing code in Kotlin" is not a realistic demand.

My experience has been that "migrate on touch" is a reasonable strategy, so if you have to make a change to a file, use the "Code > Convert Java to Kotlin" (control-alt-shift-k)

A reasonable strategy if you never ever merge two branches. If there's a non-homeopathic chance that a parallel change to the file in question might eventually pop up I'd limit "migrate on touch" to occasions when you do major rework and not just a minor touch. If it's possible to occasionally enforce a "branch singularity moment" I'd go with migrate on major rework until a branch singularity opportunity comes up and then do the bulk conversion to what I affectionately call "shit kotlin" (the endless procession of exclamation marks that faithfully recreate each and every opportunity where the java could, in theory, achieve an NPE) in one go. And leave only the cleanup of that mess to "on touch". If it later comes to parallel cleanup, that wont be half as annoying to merge, not even remotely.

What I haven't tried is "migrate on touch" with a strict rule that there must be explicit commits just before and after the conversion (plus a third commit documenting the file rename separately, before or after). That could perhaps work out well - or not help much at all, I don't feel like I could even guess.

But other than that, the intermediate state of partial conversion is surprisingly acceptable to work with, I'm not disagreeing!

Re: Retrofitting null-safety onto Java at Meta

#179

Earlier quoted context omitted.

I have definitely come to the conclusion, especially after doing a lot of work in Typescript, that class Optional is a big mistake, whether from the JDK or other libraries that preceded it. First, because exactly the type of code that the parent commenter showed. I've actually seen this in production code (and shrieked). The fact is that without language-level support, you can end up getting the worst of all worlds.…

I miss the pre-Rust days. When Haskell was HN's cool pet language, and you had to obtain at least some vague familiarity with the term "monad" to understand half the discussion threads here. I'm sorry, but I don't think you understand the purpose that "Optional" was intended to serve. And are unduly dismissive simply because it does not serve some larger purpose that was not intended.

I mean having a monadic api is nice if it's strictly enforced by both ecosystem/culture/typesystem. But with Optional being potentially a null itself, it barely improves the need to defensively program and might in fact be worse. For example, when I used a lot of Scala in a past job, Java libraries were scary to use unless you defensively wrap java functions with Try/Option/etc.

Whereas with Haskell/Rust/OCaml/etc. you can largely trust type signatures to properly encode the existence of nullability or failure.

Re: Retrofitting null-safety onto Java at Meta

#180
post #163
post #113

Earlier quoted context omitted.

Increased productivity comes in various ways. A more popular language often has a better ecosystem that helps productivity, and adding lots of language features quickly is a hindrance to huge popularity. This might not be the case for many here, but most programmers prefer fewer features than more, and the most popular languages are also often those that can be taught as a first language, which also requires restrain…

What is this argument exactly? We're arguing against the concept of features? Why are we talking hypothetically? Async/await provides immediate user value. If people didn't like it they could just use threads in a Java-like style. While some dislike the features, and other push against the sour grapes, its a popular feature found in many languages used by many developers. Java is popular and so is C# and Javascript,…

First let me say that since it's established that different programmers have different preferences, the fact that some programmers might prefer a different evolution strategy is no reason to change it, because that will always be true. A reason to change strategy is if some other one has fared better, and none has. The only languages that have arguably fared better than Java are Python and JS, and they have fewer, not more features. So there's simply no good reason to pick a different strategy that has not shown to fare better.

Now, when it comes to async/await, first let's take JS off the table, because JS has no other convenient concurrency feature, and it couldn't add threads because it would have broken most existing JS code (it could add isolates, but they're not sufficiently fine-grained, at least not in the JS world).

If Java had got async/await ten years ago, it would have been burdened with it for decades. It would have provided some value in those ten years, and extracted value for the following forever (albeit gradually diminishing). "Just don't use it" works fine for library features, but not so much for foundational language features, because programmers usually don't start with a blank slate, and don't pick the features in the codebase they work on. Therefore, all language features carry a cost, and they carry a higher cost when it comes to beginners, where this matters most.

It's hard to precisely describe what could have been, but I think most would agree that in those ten years Java didn't lose many developers to languages with async/await because they had async/await. It probably lost some developers to Python and JS for other reasons (say, Python is easier to get started with, and JS is easier for those who know it from the browser), and it didn't even lose that many people to Go (Python lost many more to Go than Java did). Considering all that, I think that Java's position in 2022 is better, now that it has virtual threads, than it would have been had it also had async/await (which would have likely also delayed virtual threads).

If I could go back in time knowing what I know now, I would have advocated against adding async/await ten years ago with even higher certainty. Back then I just believed there's a better way; now not only do I know there's a better way, but I also know that not adding async/await didn't cost us must if at all.

Going back to the original topic, Java's primary competitors -- Python and JS -- also don't have a great solution for nulls. So while I would very much like to address this problem, I see no reason to change our game plan in favour of scrambling for a quick solution. We'll tackle this issue like we try to tackle all others: methodically and diligently, and preferably after we've had time to study how well various solutions work elsewhere.

Post reply on HN