Hacker News new | ask | show | jobs
by cletus 2117 days ago
So C# definitely benefited with the second mover advantage. It learned a bunch of lessons from Java such as:

- No checked exceptions

- Properties (I wish Java would add this to the core language instead of relying on things like Lombok as it shouldn't change the IR and really is just syntactic sugar)

- Partial classes. This really isn't in contrast to Java because Java has nothing like it. But it is a neat feature for partial code generation;

- LINQ. Java eventually added streams but last I checked it still had performance issues and IMHO LINQ is just cleaner;

- Conditional compilation. This is really a huge oversight. One of the huge benefits of the preprocessor in C/C++ was conditional compilation. It's great than C# included it. It's bizarre to me that Java hasn't.

- Async/await. Honestly I still find C#'s version of this more awkward than Hack's. Experience has taught me that whenever you spawn a thread, you've probably made a mistake as subtle mutlithreading bugs are the devil and cooperative multitasking like you have in Go, C# and Hack is usually far safer and sufficient most of the time. Still, C# is still better than Java here.

- C#'s reified generics vs Java's type erasure. I think it was the right decision to break backwards compatibility here (and I don't usually say this). This was pretty early too (IIRC generics were added in C# 2.0).

But all that being said, I still think Java has a large mindshare and install base than C# by a mile. it's not sexy so it gets less attention on HN but Java is still massive.

As for Kotlin? Much like Scala I see this as nothing more than a curiosity. Android developers seem to like it but I think it's a tiny fraction of Java still.

5 comments

“One of the huge benefits of the preprocessor in C/C++ was conditional compilation. It's great than C# included it. It's bizarre to me that Java hasn't.”

The C preprocessor can make it way to easy to break code, for example when it is used for feature flags and/or multi-platform support. If you have N feature flags, you have to compile 2^N different programs. Multiply by M for supporting M platforms.

It also makes it impossible to check whether source code can be compiled. The text inside a

  #ifdef FOO
    ...
  #endif
block doesn’t have to be valid source code, but the compiler cannot know whether it has to be. I think that’s why Java ditched it, and I think that makes sense. Adding a more limited feature, like C# did, makes sense, too, though. I’m not sure C#’s variant is limited enough, though. it still is subject to that 2^N problem, but gets saved from its main problems because C# isn’t running on as diverse environments as where lots of C code evolved.
But the 2^N problem exists no matter you like it or not. Except now it's done at runtime with dependency injection instead of compile time.
That’s true, but it is done a lot cleaner than in the wild west days of the C preprocessor, where feature-specific, compiler-specific, and platform-specific #ifdef’s were sprinkled throughout the source code seemingly without much thought (but keeping things working must have taken lots of thought), nested #ifdef’s were common, and often not all cases had separate paths in the code.

Dependency injection can be made messy, too, but that takes more of an effort. You can also test injected code in isolation. That may take some effort, but those preprocessor messes only could be tested as part of the entire product.

As I said, I can see why they wanted to get rid of that. Not adding a same replacement may not have been the best choice, but I am not sure of that. Programming culture also had to change, and that sometimes requires drastic action. Apple also did that in the Mac by not providing any text mode (forces programmers to make windowed applications) and by removing cursor keys from the first keyboard (forces programmers to provide a good mouse interface)

LINQ is cleaner, but I find streams to be more elegant when considering the language as a whole (its just a pure library - no new syntax/rules/etc to learn).

I feel like a lot of languages these days trend towards "kitchen sink" languages that toss in everything and the kitchen sink in the name of clean looking code. IMHO this tends to sacrifice language elegance. This is probably why I tend to like languages like Java/Go/Clojure.

There is not new syntax for LINQ either -- it's just fluent-style via extension methods. I don't know many people that use the SQL-like syntax any more -- and it only covers a tiny amount of the functionality.
Honestly I've forgotten sql-style LINQ exists. Method-style is common.
> As for Kotlin? Much like Scala I see this as nothing more than a curiosity. Android developers seem to like it but I think it's a tiny fraction of Java still.

Java is massive for legacy reasons, but would be nice to have some statistics regarding new projects. The last two companies I've worked for had backend teams using Kotlin so I've been assuming this is also preferred by backend people. Could well be that this has was an anomaly though.

I mostly develop for Android so my own perception is obviously biased, as Java is pretty thoroughly erased from this world now.

Easy, JVM == UNIX == Browser, Java == C == JavaScript.

Anything else on the JVM is nice to have, but isn't where all the goodies are.

Google has a special interest in getting rid of Java, hence Kotlin.

In fact, it is going to be fun to watch how they will keep up with the pace of the JVM, because when the majority of key libraries move to modern Java, many of them will stop being compatible with D8/R8, forcing Android developers to use pure Kotlin libraries.

Then JetBrains needs to think how to make use of Java libraries that use the new FFI, virtual threads, inline classes, generics, SIMD vector types, while keeping the language compatible with what ART is capable of.

And now Java is benifiting from being a second mover on a lot of the features C# adopted.

See virtual threads vs async/await. Java's version is going to be better.

I always end up with a couple of Task.Run() calls, because cannot change the complete call chain to deal with async/await.
If you want to avoid changing the complete chain to Task, there’re workarounds.

You can block the caller thread waiting for the task. Deadlocks are possible with this approach due to synchronization context shenanigans, but they’re easy to fix, VS debugger is quite good at multithreading.

Another method, you can run whatever dispatcher on your main thread, in modern .NET that’s usually Dispatcher.PushFrame.

Also, if the async method returns no values, you don’t need to change the call chain, you just need to catch and handle exceptions.

Even that situation, you can still use await inside Task.Run, it should be useful than nothing.
Some circles rely on Lombok, I never used it nor plan to.

I rely miss checked exceptions on .NET, specially with libraries that provide zero documentation about their exceptions.

I've never seen the allure of lombok.

Most things shouldn't even have getters and setters apart from your @Entity classes.