Brian Lovin
/
Hacker News
Daily Digest email

Get the top HN stories in your inbox every day.

northern-lights

The biggest feature in Java 21 is the release of Virtual Threads: https://openjdk.org/jeps/444

For some reason, this is missing from the article. If there was any feature that would sway existing Golang developers to switch to Java, it would be this. It would perhaps also convince the haters of the reactive-style concurrency patterns.

impulser_

I don't think any existing Go developer is going back to Java.

I worked with Java for 10 years and switched to Go and I will never go back.

This is mostly because applications and libraries are so hard to reason about and understand due to inheritance, packaging, OOP, build tools ect compared to Go.

Go is simple. It's easy to understand, read, and maintain. The packaging is like how you would package files on your computer in single folders. The tooling is built into the language. You don't need a IDE like IntelliJ just to make it feel reasonable to work with.

Maybe all of this has changed, but most of the libraries I see in Java today still look like this.

kaba0

> This is mostly because applications and libraries are so hard to reason about and understand due to inheritance, packaging, OOP, build tools ect compared to Go.

You are just at the early phase of the project.

> Go is simple. It's easy to understand, read, and maintain

My opinion is that Go is too simple, to the point that it hinders understanding and readability. 4 nested loops with random mutability is much worse than a clear Stream api "pipeline". The 31st if err check will not be actually handling the underlying problem, while you can't forget about Exceptions -- even if they do bubble up to the main app, you will clearly get an actionable stacktrace.

> The tooling is built into the language

This has benefits, I will give you that. At the same time, I think all these new build tools are anemic and can't be used for anything more complex than interacting with the same language's standard dependencies. Gradle is not sexy, but is actually capable of doing any kind of build.

nonethewiser

Im convinced Go is just an incomplete language masquerading as a simple one.

kamaal

>>You are just at the early phase of the project.

Nah.

99% of the code written out there doesn't need layers of indirection, responsibility tossing around, Code splitting across classes, design patterns, inheritance and the class jungle that is common in Java.

Rise of languages like Go is simply majority of the people realising when they want X, they are better of writing just X. You don't have to write a generic version of X that needs to work in a dozen situations. This is for a simple reason. Most of the times, there are no dozen situations. Most, not all the times.

Most of the code I write, doesn't change all that much. If you are writing code that needs to run for decades in an industry where grifting and job hunting is a daily affair you are doing it wrong.

tuyguntn

> You are just at the early phase of the project.

Lately, I am seeing very few codebases getting supported more than 3 years in companies offering software products. Every 2-4 years services are getting rewritten, what's the point of having tool intended for 15 years, when services are deprecated in 4 years

__MatrixMan__

It would be good for my job if learned go, but I've been having a hard time because it's just so boring.

jpgvm

I did. Did Go professionally for ~5 years and went to JVM.

Go is easy, but it's surface level easy. Building and maintaining large applications in Go if you don't have a huge team is a giant pain.

JVM (especially paired with Kotlin) for me atleast has meant regaining the expressivity I was missing from Ruby and Python when I moved to Go whilst retaining (well actually usually beating) it on performance and scalability.

I lost absolutely nothing to go to JVM but gained so many things.

chrisdinn

I’ve got to disagree with this strongly. I built and operated the same large system in both languages (acquisition forced me to rewrite) and both the dev and operating cost of the JVM was much higher.

Go is a high velocity language. Code reviews on language/style issues are non existent, it’s GC is blazing fast (not our experience with JVM) and it’s really easy to read. I’ve watched many new engineers ramp up on both systems, as both operated side by side for a while, Go was inevitably faster for engineers.

What made maintaining Go hard for you?

cies

> JVM (especially paired with Kotlin)

I agree the Go lang compiler/runtime needs "a Kotlin", with more null-safey build in, proper sum types, a std lib that makes heavy use of null-safety and sum types, better story for polymorphism, better ergonomics for writing stream pipelines, and additional compilation targets (like JS, WASM). This all while proving stellar interop with all Go code, obviously.

Kotlin is quite a sweet deal.

anacrolix

Going on 12 years with Go and agree with everything.

synergy20

Go's major selling point to me is that you can ship one binary, nothing beats that.

Python, Node, Java all have to pre-install lots of dependencies before you can use them, fine for developers, not so great if you want to distribute software for people who are not software savvy, who typically just wants to download one file, install and start using it.

c and c++ can also do one executable, but, it is not as portable as Golang, and not as static-link friendly as Golang.

lenkite

> Go's major selling point to me is that you can ship one binary, nothing beats that.

You can ship one compiled binary in Java too if you want it. https://www.graalvm.org/22.0/reference-manual/native-image/

> Go is simple. It's easy to understand, read, and maintain

Go involves a lot of code repetition which makes it difficult to human-scan and maintain. Worked on both large scale Go and Java projects and I found Java projects easier and more comfortable to maintain. Had to pay a lot more attention to Go. Go is easier to write though thanks to its well-designed and extensive standard library which is possibly the best in the world, but the maintenance angle still tilts to Java. There are also more Gotchas in Go compared to Java.

chii

> not so great if you want to distribute software for people who are not software savvy, who typically just wants to download one file, install and start using it.

i think it's a flaw that wasn't considered properly in the standard java toolchain to not produce an embedded java runtime into a final packaged artifact that is self-executable.

You end up with third party tooling like: https://www.ej-technologies.com/resources/install4j/help/doc... (and https://launch4j.sourceforge.net/ too).

If oracle bundled this tool into the JDK, it would've not been an issue at all.

ashishb

Fully compiled languages have another huge advantage. If you write a tool in Python and targeted say Python 3.10 then people using Phython 3.9 might not be able to use it. So, you really can't use latest and greatest features of Python 3.11 or 3.12. However, with Go, you can build your tool in Go 1.21 or whatever, and the user does not even need a Go toolchain on their machine.

I was planning to write a small side-project to generate GitHub Actions boilerplate. And this time, I intentionally chose Go for that exact reason. https://github.com/ashishb/gabo

mdhb

I never see Dart talked about in these contexts but just to highlight a few things.

1. Compiles to native code with a single and very reasonable sized binary on pretty much any platform.

2. Compile to WASM (coming this year) if that’s your thing.

3. Excellent concurrency support with lightweight and simple mental models

4. Variables are not nullable by default thus simplifying tedious checking in your codebase.

5. Syntactically it’s the best parts of Java and JavaScript combined without all the foot guns and verbosity.

6. Full support for both OOP and functional code styles

7. Existing interop support with C, C++, Rust, Java and JavaScript and in the future WASI.

8. Fully static / compile time metaprogramming capabilities coming this year.

9. Also have maybe one of the best teams working on it that I’ve ever seen in an open source project anywhere. They put in a stupid amount of detail and care to try and keep everything pointing in the right direction at a macro level and have really strong levels of transparency around how the language is developed https://github.com/dart-lang/language

Honestly I think it’s critically under-rated and under-used. Most of its common criticisms I see about it are many years out of date.

cbm-vic-20

Since Java 9, developers can use jlink to provide a pre-packaged runtime environment bundled with the application. It's not static-link friendly or a single executable, but it does not require the user to pre-install anything or pull dependencies at runtime.

paradite

You mentioned "distribute software" but did not consider Electron for Node.

You can make an Electron app with JavaScript and ship the binary (or installer) on any platform. It's not a single executable file, but the user experience is the same.

I don't think there's an equivalent in Go that allows you build a desktop app like that (with frontend and backend both written in Go)?

saiya-jin

You can easily pack every single class file and resource into one single .jar if you desire to do so, no external dependency apart from JRE itself. Oh and of course no installation necessary, just put it anywhere with read access, all works.

At least in the past you could also make a single .exe out of it via 3rd party if you wanted, but I didnt use that for 20+ years so maybe its not valid anymore.

mathiasgredal

You just add the -static argument, if you want a fully static executable that can run on any linux distro: ‘g++ -o main main.cpp -static’

You can even go above and beyond with cosmopolitan libc v2, which makes c/c++ build-once run-anywhere: https://github.com/jart/cosmopolitan/releases/tag/2.0

There seems to be some work getting cosmopolitan libc support in Go, but it is not ready like it is for c/c++: https://github.com/golang/go/issues/51900

Edit: There can be some problems with using static, if you are using your system package manager for library dependencies. I would recommend compiling dependencies from source using CMake and FetchContent, this has solved pretty much all problems I have had with c++ dependency management.

totallywrong

> Go is simple. It's easy to understand, read, and maintain.

Matter of taste I guess, to me Go code looks ugly, it's too verbose with all that error handling every other line which hurts readability. Also the docs are often so cryptic and unhelpful, one needs to rely on examples elsewhere. I do use it though, when I need something fast in a single binary.

speedgoose

My main pet peeve with Golang is to use single letter or very short abbreviations when naming variables. The Golang documentation recommends what is considered a bad practice by most programmers communities.

arein3

Exception handling and generics are missing key pieces from go. But adding them will make go look like java.

I just wish java would add null safety in the type system in a first citizen way.

For enterprise use java has no competitors. You have c# which is microsoft trying to estabilish nash equilibrium fu*ing the developers. I am a bit worried about ever increasing complexity and a steep learning curve, but seems like this is a problem on all fronts.

neonsunset

Java is an inferior language to C#. To get a superior language you would likely have to go for Kotlin. There is no comparison, because Java gets features today that C# had for years. Not even mentioning having to retrofit green threads because adopting async/await (used by TS, Rust, Swift and other languages) is impossible at this point.

kaba0

> I just wish java would add null safety in the type system in a first citizen way

With valhalla (value types), it might just come!

guideamigo_com

Afaik, no language as great of exceptional handling as Java. I prefer Go but I have to admit, Java's checked exception handling is amazing.

turndown

Go has had generics for more than a year now

rcme

What is “enterprise” use? Plenty of enterprises use Go.

throwaway2990

This is definitely one of those. “Tell me you don’t know .NET with out telling me you don’t know .NET” moments.

mseepgood

> Exception handling and generics are missing key pieces from go. But adding them will make go look like java.

You know that Go has generics since a couple of years now?

iampivot

Coming from Spring Boot with it's 17+ level deep abstractions to Go / gin-gonic was such a breath of fresh air!

_ZeD_

With the little difference that spring boot offers you 99% of your needs, both to fetch the data from (sql, nosql, you-name-it), and to offer your interface out (web, rest), and programming style (syncronous, asyncronous), and observability, ....

while for go you'll find yourself deep in the mud of choosing what 3rd part library to use for logging and how to make it work with the rest of custom stuff you have to write

nprateem

I went the other way as I was sick of all the repetition in Go. Spring boot/JPA data repositories save so much SQL, and spring boot automatically marshals to and from JSON, form data, etc. It means you can just work on the meaningful business logic. Add on Lombok and there's even less to maintain.

pjmlp

Go is Java 1.0, nowadays 1.5, as they thankfully finally at least added some support for generics.

I am not touching Go, other than on the projects I have some customer or higher up telling me to do so.

hota_mazi

Go gives you the illusion of simplicity because it gets rid of guard rails and error checking. If you remove all error checking, of course your code is going to look a lot simpler. It's also going to be a lot more wrong and crashy.

Wait until your code base grows, your team grows to >10 developers, and you will understand what I mean.

Java (and preferably Kotlin) are a lot more serious about making sure your code is robust before it compiles.

segfaltnh

Wait, are you implying Go doesn't have error checking? In what way? It has famously verbose error semantics.

sgloutnikov

I've heard this argument before, and I point out Kubernetes to them. Is that code base complex enough, because it's pure Go doing just fine and runs on plenty of systems?

javcasas

> Java (and preferably Kotlin) are a lot more serious about making sure your code is robust before it compiles.

You are making great points for Rust, Ocaml and Haskell.

brightball

Java 21 has me anticipating the next JRuby release because of Virtual Threads. Charles Nutter gave a talk about JRuby in August where he showed a demo of the impact on Ruby fibers and it’s pretty significant.

There’s a lot that I really like about the JVM and it’s tooling, I just don’t like writing Java code anymore. JRuby kinda gives the best of both worlds.

Here’s the talk. Virtual thread demo is around the 45 minute mark.

https://youtu.be/pzm6I4liJlg?si=GtxQ4MThEaNDfC67

kaycey2022

I loved this. Is there any post comparing Ruby performance with JRuby?

mike_hearn

There are lots. Usually these days, also comparing with TruffleRuby, which also runs on the JVM.

This one is a couple years old now but you get the idea:

https://eregon.me/blog/2022/01/06/benchmarking-cruby-mjit-yj...

I don't know where to find more up to date benchmarks.

kaba0

How does jruby relate to truffleruby?

brightball

He actually talks about that during the video as well. Short answer is “it depends”.

IIRC Truffle is dramatically faster in some benchmarks but slower in others. And there are some usability aspects of the language that are negatively impacted.

ksec

Wondering if anyone is using JRuby on Rails in production.

brightball

He listed off a bunch during the talk.

g9yuayon

> If there was any feature that would sway existing Golang developers to switch to Java, it would be this.

Not sure about this, but a trajectory question: why does the Go community have so few concurrent containers but Java community does? I mean, even `sync.Map` is specialized for two specific use cases instead of like Java's ConcurrentMap that is of general purpose. And In Java there are concurrent sets, queues, barriers, phasers, fork-join pools, and etc. I'd assume that even with Go's go routines, we can find great use of such containers, right? At least fork-join is not that trivial to implement. And using mutex everywhere seems just so... low level.

I understand that there are 3rd-party implementations, but concurrency is so tricky to get right that I would be really hesitant to adopt a 3rd-party package, unless it is as mature as Java's JCTools or Google Guava, both of which are also backed by a large number of users and developers.

segfaltnh

Because the predominant patterns are to do concurrency with message passing, so having multiple goroutines trying to mutate the same container is the exceptional case (though as you say, mutexes are the answer when needed). Another reason is containers are embedded features not stdlib, which has huge implications to how willing someone is to go against the grain, for better and worse.

za3faran

Ironically, golang does not have constructs to build immutable types to pass them around, while Java does (records).

bbkane

Yet another reason is that you really want generics for container types and Go's generics are relatively new

throwaway2037

What concurrent data structures that Google Guava offer than the standard library does not?

insanitybit

`Executor.newVirtualThreadPerTaskExecutor` versus `go` really gets to the heart of why I think that Go developers aren't going to be switching.

edit: Sorry, it's actually:

    try (var executor = 
        Executors.newVirtualThreadPerTaskExecutor()) {
       executor.submit(...)
    )
instead of `go`

erik_seaberg

You’re likely to need a lot of boilerplate that “go” statement won’t generate: pass and wait for completed results, report errors, timeouts, cancellation, bounded parallelism, pushback, service monitoring.

insanitybit

My comment is perhaps too glib. I'm not trying to say that Go is better, I frankly like Java more. I only mean that if I'm in a Go mindset and I read the linked document on virtual threads, and I see that code example, I'm going to close the tab.

undefined

[deleted]

mattbee

Starting a virtual thread in Java isn't that clumsy:

   Thread.ofVirtual().start(() -> System.out.println("Hello"))

zaphirplane

The go example is missing the channels, select, wait group, context, cancellation

Doesn’t sound like a fair comparison thou the Java version is missing things

insanitybit

> The go example is missing the channels, select, wait group, context, cancellation

The Java version would require all of those in the exact same way though.

mike_hearn

If you want to use that with more concise syntax, there is Kotlin for that. In which case abstracting that is one line of code tucked away in a utility file:

    fun <T> go(body: ExecutorService.() -> T): T =
        Executors
            .newVirtualThreadPerTaskExecutor()
            .use(body)
Now you can write:

    val result = go {
       val result1 = submit { slowOp() }
       val result2 = submit { blockingOp() }
       result1.get() + result2.get()
    }
or words to that effect. You can also reduce it a lot in Java too, as mentioned by another commenter.

    class Shortcuts {
        static <R> R go(Function<ExecutorService, R> task) throws RuntimeException {
            try (....) { return task.apply(service) }
        }
    }
and then you can write:

    var result = go(service -> {
        var result1 = service.submit(() -> slowOp());
        var result2 = service.submit(() -> blockingOp());
        result1.get() + result2.get();
    });
using static imports.

Now if you're making a cultural point then sure, Java APIs tend to be named quite explicitly. It has advantages when searching or auto-completing, and it's easy to wrap with aliases if you want to abstract a boilerplate pattern away.

ivan_gammel

Wrap it in class Go { static void go(…) } and it will look better with “import static”

insanitybit

I assume `Go` would have to be Closeable and you'd still need the try-with-resources, right?

kaba0

So basically executor.submit(). And you still have a language that is significantly less verbose than go, objectively.

insanitybit

Yes, if you ignore more than half of the code it's quite concise.

foolfoolz

go will always lose vs java when it comes to code verbosity

lenkite

Extend that to a full function and the Go code will be 2x more verbose than Java with all the `if err != nil` stuff.

cgh

Java 21 also previews structured concurrency (https://openjdk.org/jeps/453), which uses the virtual thread implementation. It seems really nice, at least from reading the examples, and removes a number of pain points when dealing with thread-based concurrency.

jjav

> The biggest feature in Java 21 is the release of Virtual Threads

Is there a good honest writeup on why is this interesting? Very curious.

The earliest JVMs had this, green threads. Performance was terrible so it was eventually dropped.

I want to fully utilize every CPU and every core I have, why do I want green/virtual threads again?

kaba0

The JVM automagically swaps out blocking network calls executing in a virtual thread to a non-blocking implementation, so potentially many other requests can be served in the same time.

So you get the benefit of async frameworks, with none of the negatives: the blocking model is trivial to reason about, read, maintain, and works perfectly with tooling (you won’t get an exception in some WhateveScheduler, but at the exact line where it actually happened. Also, can step through with a debugger).

jjav

Thanks. Any good books that cover the current java performance landscape but is grounded in the history of what came before?

I used to be heavily involved in java-based server performance and scalability work back in the 90s and 00s but have been working on other things the last decade. But it would be fun to learn more about where things stand.

okeuro49

> I want to fully utilize every CPU and every core I have, why do I want green/virtual threads again?

If your task is CPU bound, then virtual threads don't gain you anything.

On the other hand, if your task is IO bound, e.g. for a webserver, virtual threads make it trivial to spin up a new thread per request, for massive workloads.

https://en.m.wikipedia.org/wiki/C10k_problem

mike_hearn

It's not the same. The earliest JVMs weren't really thread safe at all but machines were single core so it didn't matter, you could just cooperatively multi-task.

Later HotSpot became thread safe (and fast - a rare achievement), so started using real OS threads so it could go multi-core.

Virtual threads are M:N threading. There are multiple native threads so you can exploit all the cores, and also user-space runtime managed stacks so you can pack way more threads into a process.

jjav

> The earliest JVMs weren't really thread safe at all but machines were single core so it didn't matter, you could just cooperatively multi-task.

In the 90s we didn't have multiple cores but we had multiple CPUs. I started using java in '96 on a 2 CPU SPARC and the lack of real thread support was limiting. When green threads was dropped in favor of real (OS) thread support there was much rejoicing. I worked primarily in server performance back in those days.

> Virtual threads are M:N threading.

Solaris had M:N threads early on but it also was dropped.

tommiegannert

For me, it's the difference between code structure and resource usage.

Green threads are for a cleaner code structure. async/await comes quite close, but requires function coloring, creating high coupling. The threads have to have low overhead, so that I don't have to think about whether I should create a new one or not.

Kernel threads are for parallelism and scaling across cores.

waffletower

From my vantage as a Clojurist, virtual threads on the JVM seem to have very minimal benefits. All I can think of at the moment, is that we could have thread-safe JDBC connectors: https://medium.com/oracledevs/introduction-to-oracle-jdbc-21...

eduction

I mean couldn’t you have the performance equivalent of core.async’s go except with the ability to take across cross function boundaries? That seems pretty huge.

jayd16

I don't think Golang devs are waiting to switch to Java if only making a lot of threads was easier.

Honestly, its way too early. Virtual Threads will need a a "killer app" (in this case a killer framework) to pull in devs.

kaba0

Like Helidon Nima, Micronaut, Quarkus or recently, Spring?

jayd16

I haven't really used or heard of the others but Spring was already quite good and you really didn't need to think about threads very much. This might help some benchmarks but, again, who is pining for "Spring but with more threads?"

I'm thinking something that would be a clear differentiater such as a multithreaded GUI framework.

bcrosby95

The great thing about virtual threads is they're basically a drop in replacement for threads.

za3faran

For cases that are IO bound yes. Threads still have their place (another thing that golang doesn't have).

undefined

[deleted]

msgilligan

The title of the blog post is IMO a poor choice. The (hidden) subtitle of the post is "Algebraic data types in Java" which is much more descriptive of the content. A better title would have been "Algebraic data types in Java 21".

Perhaps because of the title, many/most of the comments here are off-topic. I was hoping to see more discussion about algebraic data types, strengths and weaknesses of the Java implementation, technical comparisons to other languages, etc.

fatfingerd

Yes, I'm also not really sure I want to see algebraic types in Java even though I would prefer if a language that focused on algebraic types was more popular.

All the existing Java code doesn't go away, so is it really going to be nicer to have code like this mixed randomly into that?

owlstuffing

Of all the features most Java devs (and ex-Java devs) desire, algebraic types are near the bottom.

How about intersection and union types? (NO, sealed classes are not a substitute for unions).

But, yeah, in my view JDK 21 is a a disappointment. I rarely want pattern matching, but I would like properties please and records are nice, but actual tuples are more useful. Etc.

smrtinsert

I would love a union type in Java, but I still enjoy the language, community and especially development experience. I wouldn't recommend it for everything, but it's sturdy by design and enjoyable for when it fits.

mx_02

I'd love if interfaces worked as in typescript.

As long as the object signature matches the interface as parameter then you can use it.

mu53

[dead]

msgilligan

Well hopefully it won't be mixed in _randomly_.

I have some use cases in mind and think it will be very helpful. I have been converting a 10+ year-old code base to modern, functional-style Java and believe that sealed types and pattern matching will help us further simplify our code and eliminate more usages of nullable values.

A challenge for us will be that we need the core library to stay on JDK 8 for a while. But, in general, I am finding that you can implement a JDK 8 library that works well with newer JDKs if you are careful (and willing to write more verbose code using the older syntax/libraries to support the newer paradigms)

wscp-dev

I did title it that way at first but ended up changing it at the last second, ended up shunting it off course.

msgilligan

It's a great post. Thank you for writing it!

wscp-dev

thank you for giving it a read!

adrianmsmith

The "Sealed classes" feature, as described here, just feels all wrong to me.

They are saying that if you have a (normal) interface, anyone can create a new class implementing it. So if you do

    if (x instanceof Foo) {
        ...
    } else if (x instanceof Bar) {
        ...
    } ...
then your code will break at runtime if someone adds a new class, as that code won't expect it. So the article is saying the solution is to use the new "sealed" interfaces feature, so nobody can create any new classes implementing that interface, and your "if" statement will not break.

Surely object-oriented programming already thought about that and already solved that? (I know object-oriented programming is out of vogue at the moment, but Java is an object-oriented language.)

The solution there is to add a method to the interface, and all your classes implement that. Then rather than having a massive if/switch statement with all the options, you call the method.

That is better than preventing people from extending your code, it allows them to extend your code. They just have to implement the method. And the compiler will force them to do that, so they can't even accidentally forget.

The example given of color spaces (RGB, CMYK etc.) is a great example. I can absolutely imagine writing code which uses color spaces, but then some user or client having a need to use a weird obscure color space I haven't thought of. I wouldn't want to restrict my code to saying "these are the color spaces I support, due to this massive if/switch statement listing them all, the code is written in such a way that you can't extend it".

valenterry

> The solution there is to add a method to the interface, and all your classes implement that.

What if you don't know all methods that you will need in advance?

That is the problem and this problem is solved by sealed classes. However, by doing so they introduce a new problem: what if you need more extending classes and you don't know all of them in advance? Which raises the question: is there a way to achieve both?

This problem is called expression problem. [1]

There are (statically typed) languages that are able to solve the expression problem, Java is one of them [2]. However, unfortunately the way to do that in Java is (still) very complex and unergonomic, hence rarely used. Languages like Haskell or, if you want to stay in the JVM world, Scala do much better here.

[1] https://en.wikipedia.org/wiki/Expression_problem [2] https://koerbitz.me/posts/Solving-the-Expression-Problem-in-...

pkolaczk

The solution with sealed classes also allows anyone to extend the code, but in a different dimension than the solution with an interface method.

The solution with interface method and virtual call is very inflexible when you want to add new operations instead of adding new classes. If you want to just add one new operation, then you have to go to all the implementations and add new methods. And you possibly break the implementations you don't have access to. And all those methods must be defined in a single class, even if they are unrelated to each other. This seriously degrades code readability (and performance as well - those vcalls are not free either).

The sealed class extends much better in this case. You just add a new switch in one place and done. No breaking of backwards compatibility.

This is the famous expression problem.

https://pkolaczk.github.io/in-defense-of-switch/

andrekandre

  > If you want to just add one new operation, then you have to go to all the implementations and add new methods.
not necessarily, if you have extension methods (kotlin, swift) you have the option to extend the interface and only override the specific implementation when needed

mrkeen

I understand what you're recommending, and I've seen Bob Martin talk about it extensively (polymorphic dispatch instead of instanceof), but it's something I disagree with.

To do this kind of polymorphic dispatch, objects have to deal with multiple concerns within themselves.

In a video game, a Car might have .render(), .collide(), .playSound(). Later on you can add a Dog, which also has those three methods, and you don't need to edit/recompile the Renderer, the PhysicsEngine, and the SoundEngine. And other programmers can add additional entities like this without introducing bugs into my precious code! What's there not to love?

Well, now both my Car and my Dog need to know about graphics, physics, and sound. And these entities don't exist in isolation. Cars and Dogs need to be rendered in the right order (maybe occluding one another). They'll definitely need to check for collisions with each other. And (something which has actually happened to me in a Game Jam) my sound guy is going to need to step into all my objects to add their sound behaviours.

I would much rather work in the Physics.collideAll() method, and have it special-case (using instanceof) when I'm thinking about physics, and work in the Graphics.renderAll() method when I'm thinking about graphics.

A more common example I see in day-to-day backend Java web dev: when I'm sitting in the (REST) Controller deciding how to convert my Java objects into HTTP responses, I much prefer it if I can consider them all in one method, and map out {instanceof Forbidden} to 403, {instanceof NotFound} to 404, etc., rather than putting getCode() (and other REST-specific stuff) into the Java classes themselves.

jppittma

The OO solution is to have a PhysicalObject class that dog and car both inherit from (or use via composition).

mrkeen

Then there's even more scattering around of the logic.

Good way:

  PhysicsEngine {
    List<Entities> entities;
    doCollisions() {
      // Logic involving instanceof
    }
  }
Bad way:

  PhysicsEngine {
    List<Entities> entities;
    doCollisions() {
      // Delegate to whomever.
      entities.forEach(e -> e.collide());
    }
  }

  Dog {
    collide() {
      // What the hell can I do here?
      // I don't know about the rest of the world
    }
  }

  Car {
    collide() {
      // What the hell can I do here?
      // I don't know about the rest of the world
    }
  }

Worse way:

  PhysicsEngine {
    List<Entities> entities;
    doCollisions() {
      // Delegate to whomever.
      entities.forEach(e -> e.collide());
    }
  }

  Dog : PhysicalObject {
    collide() {
      super.collide();
    }
  }

  Car : PhysicalObject {
    collide() {
      super.collide();
    }
  }

  PhysicalObject  {
    collide() {
      // Not only do I not know about the rest of the world
      // I don't even know how *I* collide, because what am I?
    }
  }

kaba0

It doesn't always make sense to allow extending -- String is `final` for a reason (and one might even argue that final should be the default, and one should explicitly mark with `open` classes that can be subclassed).

The stereotypical FP example for sum types are a List -- there you only have an Element<T>(T head, List<T> tail) and a Nil(). There is no point extending it, it would, in fact, result in incorrect code in conjunction with all the functions that operate on Lists.

Also, the Visitor pattern, which is analogous to pattern matching is very verbose and depends on a hack with the usual method dispatch semantics of Java. I do think that pattern matching is several times more readable here.

erik_seaberg

In a world of untrusted code and SecurityManagers, it was critical that strings be immutable so a data race couldn’t bypass a policy decision. The JVM doesn’t have immutable arrays, so string methods carefully protected the embedded array from tampering, and couldn’t be overridden.

Most classes don’t have this problem. The original authors of a class don’t know what I’m trying to do, and they don’t bear consequences if I (a consenting adult) get it wrong.

nine_k

There are valid use cases for that.

Consider a security interface of some sort, e.g. such that validates a security token.

With a normal interface, it is easy to implement it and ignore the token (allow all), siphon off the token, add a backdoor, etc. If a class doing that is somehow injected where a security check is done, it can compromise security.

Now with a sealed interface, there cannot be new, unanointed implementations. If you get an object that claims to implement that interface, it's guaranteed to be one if the a real, vetted implementation that does the actual security check, not anything else. You've just got rid from a whole class of security bugs and exploits.

kaashif

Nice article as someone familiar with sum types but not sum types in Java.

I don't know if sum types alone are enough to get me to like Java, pervasive nullability is still around and even rears its head multiple times in this article.

leapis

Nullable is a huge issue in Java, but annotation-based nullability frameworks are both effective and pervasive in the ecosystem (and almost mandatory, IMO).

I'm really excited about https://jspecify.dev/, which is an effort by Google, Meta, Microsoft, etc to standardize annotations, starting with @Nullable.

kaashif

This can never be as effective as changing the default reference type to be not nullable, which would break backwards compatibility, so you can never really relax.

I know Kotlin is basically supposed to be that, it has a lot of other stuff though, and I haven't used it much.

martindevans

That's basically what c# has done. But it's implemented as a warning which can be upgraded to an error. I think it might even be an error by default in new projects now.

foolfoolz

they won’t change the default reference type to non null. might take a few years but you can see their planned syntax here: https://openjdk.org/jeps/401

wayfinder

I hope they succeed. So many people have tried.

mrkeen

Not having nulls is easy.

Persuading Java devs not to use nulls is hard.

mrkeen

I was just writing about nullability annotations!

https://news.ycombinator.com/item?id=37534184

netheril96

Nullable annotations don’t work with well with generics, or at least those tools I use.

wscp-dev

With valhalla, we will have explicit nullability as well, so that problem will also be handled.

pharmakom

It's also not expression orientated yet.

kaba0

Fortunately switch has now an expression variant. But I would die for an if-else expression (very subjective, but I would prefer a more verbose if-else to ternaries), and especially a try-catch expression!

aranchelk

> Why do we call them product types anyway?

Answer in the piece is not wrong, but put more intuitively and succinctly: the total number of possible value (inhabitants) in a product type is the product of the quantity of inhabitants of the constituent types.

Replace the “product”s with “sum”s and it works too.

Interestingly, the total number of unique functions (judged only in terms of input and output) from a -> b can be found by exponentiation, (inhabitants of b) ^ (inhabitants of a).

trenchgun

>Answer in the piece is not wrong, but put more intuitively and succinctly: the total number of possible value (inhabitants) in a product type is the product of the quantity of inhabitants of the constituent types.

More intuitively and succintly: product type is equivalent to a cartesian product of sets

tubthumper8

Maps (and lists) are other examples of exponential types. Intuitively this should make sense that these are the same as functions because any pure function could be (theoretically) replaced with a map lookup of pre-computed values. In this context, list is a special map where the keys are integers.

Writing it out mathematically, given a List of Bool, the left-hand side is the number of elements and the right-hand side is the total possibilities.

- 0 : 1

- 1 : 2

- 2 : 4

- 3 : 8

- 4 : 16

- 5 : 32

and so on

wscp-dev

Ill add that in, somehow forgot about that golden bit of info

logicchains

I can't wait 'til Project Valhalla is complete and Java finally gets value types. Then with sum types, value types and goroutines it'll be one of the nicest languages out there.

dzonga

java was never really a bad language.

the people were. if not massive over-engineering. too many abstract concepts that make it hard to grasp a codebase.

code voodoo - in the form of reverse GOTO statement i.e annotations

DI frameworks .

what needs fixing is not the language but the ecosystem. there needs to be a "reformation" movement within the java ecosystem.

yeah people migrating to kotlin or clojure or scala isn't enough.

_gabe_

100% agree. You can create a HammerFactoryFactory to churn out HammerFactories in any language. But the ecosystem in Java (and C# is similar imo) promotes and encourages this type of problem solving.

The one thing Java really does need is free standing (or namespaced) functions though. Sometimes I don’t want a class, what’s wrong with a function in a module or namespace in that case?

MagicMoonlight

A class is a module/namespace. You can make a class just to have functions in it.

_gabe_

It’s not though. In lots of languages a namespace can span multiple files, whereas a class must be declared in a single file[0][1]. Modules can usually contain a collection of functions and classes. And namespaces can also contain multiple classes/structure/functions and sometimes modules depending on the language[2].

[0]: https://www.typescriptlang.org/docs/handbook/namespaces-and-...

[1]: https://learn.microsoft.com/en-us/cpp/cpp/namespaces-cpp?vie...

[2]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...

fooyc

The author cites this to justify the need for Records:

> Most Java objects set every field to be private and make all fields accessible only through accessor methods for reading and writing.

> Unfortunately, there are no language enforced conventions for defining accessors; you could give the getter for foo the name getBar, and it’ll still work fine, except for the fact that it would confuse anybody trying to access bar and not `foo'.

Scala supports pattern matching on objects implementing the `unapply` method.

Is this considered harmful? Why didn’t Java follow this route?

wscp-dev

it's a matter of standardisation again. Java's standard is like C++; ponderous. The record pattern jep indicates in final footnotes that something like unapply may be in the works, so all hope is not lost.

undefined

[deleted]

YetAnotherNick

Java was always a great language. It's the enterprisy ecosystem that make me want to throw up. To implement a line of logic, I have seen dozen classes and interfaces.

earthboundkid

道生一,一生二,二生三,三生萬物。萬物負陰而抱陽,沖氣以為和。

The Function gives birth to the Unit type. The Unit type gives birth to the Boolean. The Boolean gives birth to the Value type. The Value type gives birth to the Top type. Each Top type contains 0s and 1s, thereby bringing harmony to the computation.

truth_seeker

Golang is simple, smart and opinionated subset of Java/C#.

Everything moves slowly in corporate world. It will take another at least 2-3 years for large community of Java ecosystem and average devs to adopt Java 21 and capabilities.

kaba0

Go is just a dumb subset hyped up as simple, but it is useless and slowly it will have to introduce all the remaining pieces in some ugly way as they didn’t plan with them ahead of time - see generics.

truth_seeker

That is exactly what I used to think when I started with it. But I changed my opinion after 6 months with Go and eco-system. It's not ugly, it's different and much more concise.

kaba0

It is more verbose than java by all objective counts.

Daily Digest email

Get the top HN stories in your inbox every day.

Java 21 makes me like Java again - Hacker News