Hacker News new | ask | show | jobs
by raincole 125 days ago
> Every time there’s a new kind of bug, we add a language feature to prevent that kind of bug.

That's why learning more academic, 'non-practical' aspects of computer science is sometimes beneficial. Otherwise very few will naturally develop the abstract thinking that allows them to see uncaught exception and null pointer are exactly the same 'kind of bug.'

Anyway the author got it completely upside down. The stricter mental model of static typing came first (in more academic languages like Haskell and Ocaml). Then Java etc. half-assed them. Then we have Swift and Kotlin and whatever trying to un-half-ass them while keeping some terminology from Java etc. to not scare Java etc. programmers.

2 comments

I suppose it's somewhat accurate to claim that Haskell and Ocaml historically preceded Java (or even Objective-C). But Java wasn't inspired by those academic languages, but C: a then widely used real-world language with only partial static types.

(Not saying Java's attempt to remedy C's problems wasn't half-assed — it was.) The trend to plug holes is primarily motivated by empirical evidence of bug classes. Not by elegance of academic research.

As Bjarne Stroustrup famously quipped:

> “There are only two kinds of languages: the ones people complain about and the ones nobody uses.”

Swift, Kotlin, Rust, C++ are attempt to become languages that everyone complains about, not Haskell or Ocaml.

As someone not familiar with Haskell and Ocaml, which parts of Java are poorly implemented?
There are many, but one particular example is the type syatem.
Explain how
Not OP, and not sure about OCaml and Haskell, but one example where Java's type system is unhelpful/incorrect is mutable subtyping.

E.g. Java assumes Cat[] is a subtype of Animal[]. But this only holds when reading from the array. The correct behavior would be:

- `readonly Cat[]` is a subtype of `Animal[]`

- `writeonly Cat[]` is a supertype of `Animal[]`

- `readwrite Cat[]` has no relationship with `Animal[]`

But Java doesn't track whether a reference is readable or writable. The runtime makes every reference read-write, but the type checker assumes every reference is read-only.

This results in both

- incorrect programs passing the type checker, e.g. when you try to write an Animal to an Animal[] (which, unbeknown to you, is actually a Cat[]), you get a runtime exception

- correct programs not passing the type checker, e.g. passing a Animal[] into an writeCatIntoArray(Cat[] output) function is a type error, even though it would be safe.

(Although all that is assuming you're actually following the Liskov substitution principle, or in other words, writing your custom subtypes to follow the subtyping laws that the type checker assumes. You could always override a method to throw UnsupportedOperationException, in which case the type checker is thrown out of the window.)

Interestingly, AFAIK Typescript makes these types both subtypes and supertypes at the same time, in the interest of not rejecting any correct programs. But that also allows even more incorrect programs.

Did you mean arrays instead of lists? Arrays behave as you describe (with ArrayStoreException when you write a wrong value to an array). List<> is invariant WRT its type parameter.
Yeah, sorry, you're right. I have edited my comment. Thanks for the correction!
Another issue is that Java's initial containers were type-less and were then type generics were retro fitted as erasures.
All type checkers either permit incorrect programs, reject correct programs, or are turing complete.
I can't make a Mappable interface, and have my classes implement map(f). Because map(f) will necessarily return Mappable, not my class itself. So no method chaining for me.

Also null. Yeah I know it's contentious. People don't want to let go of it. Since learning to hate null, I've also lost any nuance in my ability to explain why it's bad. Because I know longer see it as 'subtly-bad' or 'might lead to bugs'. It's just plain, on-the-surface-wrong. One might as well have named it 'wrong' rather than 'null'.

'Null' is the thing which it isn't. I can write business logic that says every Person has a Name. Once you admit null into the mix, I can no longer make that simplest of statements. My autocomplete now lies to me, because person may or may not implement the method .name().

"But how will I half-arse instantiate a Person? I don't have a Name, yet I want to tell the computer I have a Person?" It makes me happy that you can't.

"I wrote a function that promises to return a Person. I was unable to return a Person. How can I tell the computer I'm returning a Person even though I'm not?" Glad that you can't.

Avoiding null is one of those things the FA complains about — you'll make your language three times as complicated to avoid it, and that might not be a good trade-off.
It's not really about the implementation of Java (might be bad, I don't know). It is the specification.

- People talked about null being an issues and that is a big one.

- The entire idea of OOP extremism Java implemented was a mistake - though just a consequence of the time it was born in. Much has been written about this topic by many people.

- Lacking facilities and really design for generic programming (also related to the OOP extremism and null issue

So much more more you can find out with Google or any LLM