Java 21 makes me like Java again(wscp.dev) |
Java 21 makes me like Java again(wscp.dev) |
Dead.
In many ways Java’s boringness is a feature that keeps it easily maintainable, at a low - easily recruitable - skill level.
Go isn't like that, or at least wasn't when G+ was still around.
Nowadays I don't get in touch much with people, but when I create issues Go people are usually not as unfriendly as Java/JVM people.
No idea how you have managed to come to such a conclusion, or what kind of experienced you might have had. I have been part of some of the core JSR/groups, as rather independent/unaffiliated (not oracle/google/hp/ibm/twitter) - there was no gate keeping, elitism or anything alike.
I'd consider mailing lists extremely professional, respectful, even nice.
There are always people like that when groups get big enough.
The thing I love about the JVM ecosystem is the engineering culture. The quality of the platform and most libraries is very high, and well designed.
I don't love the Java language, but it can get the job done.
Maybe it's true for Haskell or Rust. But Java?
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.
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.
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.
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.
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.
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.
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.
I am not touching Go, other than on the projects I have some customer or higher up telling me to do so.
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.
If they did one thing exactly right on the language level, it's the [lack of] OOP: interfaces in their implementations, and no inheritance, overridden methods, covariance / contravariance games, etc.
You can of course write in Java in that style: only extend interfaces, never inherit classes. But many libraries, including the standard library, actively refuse to cooperate.
How does that vary from Java?
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.
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.
edit: Sorry, it's actually:
try (var executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(...)
)
instead of `go` Thread.ofVirtual().start(() -> System.out.println("Hello"))Doesn’t sound like a fair comparison thou the Java version is missing things
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.
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?
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).
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.
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.
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.
Honestly, its way too early. Virtual Threads will need a a "killer app" (in this case a killer framework) to pull in devs.
Also, was that a clean build?
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.
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?
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.
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)
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".
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.
I'm really excited about https://jspecify.dev/, which is an effort by Google, Meta, Microsoft, etc to standardize annotations, starting with @Nullable.
I know Kotlin is basically supposed to be that, it has a lot of other stuff though, and I haven't used it much.
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).
More intuitively and succintly: product type is equivalent to a cartesian product of sets
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
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.
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?
> 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?
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.
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.
Uint32 a,b,c;
c = a.plus(b);
in what you describe, once project valhalla is complete?Signed, a dev who would never willingly choose Java over Kotlin for anything ever again.
This backward-compatibility does comes with costs (e.g. non-reified generics)
Also, if you're a library developer and you want to create a JVM library that can be used by Scala, Kotlin, etc. developing in Java is often the best choice. For one thing it avoids any dependencies on the standard libraries of those other languages.
Disclaimer: I like Kotlin and Scala, but mostly use Java.
[0]: https://arrow-kt.io
C# keeps being the language people are looking for but don't know about.
edit: I will say that as a Java developer I am grateful every day for the improvements to the language. Java is a very impressive language and I have a lot of respect for the people working on it.
And here I thought the foundation of functional programming was functions, which Java still doesn't have.
Seriously, functional programming is about functions, not types.
different people mean different things when referring to functional programming.
Some people believe that functional programming are using higher-order functions like `map`, `reduce`, `forEach`, etc, which takes a function as a parameter, instead of doing imperative loops.
Some people, in addition to above, believe that functional programming is about creating functions that take a certain 'shaped' parameters, to allow for automatic checking.
And lastly, the "real" functional programmers are people who believe in referential transparency in your functional program.
> Seriously, functional programming is about functions, not types.
Well it does have methods and it does have "Functions" (not to mention "Bifunctions", whatever those are). And there's certainly nonsense around exceptions and referring to outer variables. And no currying.
But if I understand you correctly, you real complaint is about not having effect-free functions, right? But then it becomes about the type system again, because that would be the mechanism to prevent effects.
For me bigger feature is pattern matching for switch and records.
Go, Rust, Ruby. These three cover all cases.
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-...
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.
> 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 neededTo 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.
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.
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.
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.
I can’t wait for 21, but I’m not sure when we’ll switch. At least it will be trivial compared to leaving 8.
But, yes, namespaces are still mapped to paths, it's not like they rewrote the JVM to use blockchain or something
If you run a bunch of different microservices with distinct allocation profiles, all with high allocation pressure and performance constraints, and you've accomplished this w/ the help of a very fine-tuned CMS setup, migrating that over to G1/ZGC is non-trivial
Compound this with multiple dependencies that exhibit this issue. If you have a "legacy" application that does not require active development, there's zero business incentive to invest into the upgrade. Unless you could prove value in having the upgrade, it just doesn't get prioritized.
Hard to overstate the mindshare that it lost for that
With that said, it is a cool language and platform, that is indeed underhyped.
Java eco system is so vast, and of top notch quality, with excellent documentation available for free. It enables businesses to move faster, at a pace most other languages cannot offer. It's not perfect, especially I find data sciene libraries lacking in Java compared to what the Python eco system offers. But depending on the task I try to choose the right tool for the job, not the same language for every job.
C# and .NET are most heavily invested in by Microsoft which owns and steers its development, that is true. It is also true that JVM world sees investment from multiple MSFT-sized corporations.
And yet, despite the above, it keeps moving forward and outperforming Java on user experience, performance and features despite being worked on by much smaller teams. I think it stands on its own as a measure of a well-made technology.
In addition, you can look at source code and contribute yourself, 90% of what makes .NET run is below. Almost all development happens in the open:
https://github.com/dotnet/runtime
https://github.com/dotnet/installer
https://github.com/dotnet/roslyn
https://github.com/dotnet/aspnetcore
Could Microsoft do a better job at making it even more community-facing and attempting to make the .NET foundation as a sole owner and steering committee of the language itself? Sure. But it's not that bad either today. Quick reminder - Oracle is not exactly a saint, perhaps even worse (MSFT has never gotten into any litigation even remotely related to .NET or C#).As for career opportunities, as other commenters would note, this is highly specific to a region and does not translate globally. Again, we are discussing the "how good the language/platform is" first and foremost. I don't see startups adopting Go because of the market or trusting Google not to rug pull them...so perhaps we can do a better job so the next language of choice they pick is C#, which has much higher ROI in the hands of the good developers (for example, it can be very easy to adopt as a second language if you are well versed in Rust).
In my limited 6 years of professional experience as a software engineer, I have never met a single person who writes C# or .NET.
Every time I point out about C# most programmers are saying about evil Microsoft and being locked in the ecosystem. Most programmers I've met do not even know that .NET Core is MIT open-source and runs on any device.
I really wish C# would be more widely used because it is amazing, and for sure 10 times better then Java.
We have an ODATA client that we build our selves to use in our React frontend. Now, this would obviously not have been something we also used on the backend if we knew what we know now, but it’s the perfect example to illustrate my decades of experience with C#. We had the client because we use a lot of Office365 and BC36 APIs and they are ODATA, and since we had the client we figured we might as well use ODATA on our internal APIs. Which was all well and good until we tried mixing EntityFramework, ASP versioning and ODATA together. These are all Microsoft packages mind you, and they just don’t work together. They each use the Model builder magic that C# comes with, but they each use it differently enough that things become a nightmare. Which basically sums up why C# has been a terrible language for its entire existence.
If you never get to those breaking points, then C# is fine. But when you use it for what you’d assume it was intended for, well… At least the ODATA experience sort of triggered my C# PTSD from back when I wrote an admin system to maintain all the thousands of school printers and computers in a municipality, which was hefty Active Directy work and an absolute nightmare to the point where I literally had to either extend or rewrite half the Microsoft packages because they either weren’t finished or outright terrible (again likely because they weren’t finished). Similar to how the ODATA, ASP versioning and EntiryFramework packages aren’t really finished right now. I mean, I can look at the road map for EF and see some of our issues as planned fixes on who knows when. Anyway, since those days more than a decade ago, it’s become obvious that if you want to do anything AD related then you need to use PowerShell and not C#, really, C# doesn’t even run in Azure runbooks and Python does so it’s obvious that Microsoft themselves don’t really use a whole lot of C# for that part of IT operations internally. And I think it’s frankly the same with a lot of things. The ODATA package seems to fall in that same “not used by Microsoft” box. Because it appears to be some sort of “fork” that’s mostly maintained by a single employee in China.
So yes, if you just use regular ASP and EF without versioning or ODATA to do very standard CRUD monolith APIs it’s a language that’s both fast and productive. Sure you’ll still need to do all sorts of silly things with your Azure DevOps pipelines to get EF migrations for Microsoft’s own SQL server to work, but hey, you can. But if you’re actually going to be doing things that takes it beyond it’s “basics” then no, C# is really not the language people are looking for but don’t know about.
I’m also partly in the “Java is sort of 10 years late with 21” club, but it’s not like anyone has switched away from Java in those 10 years, and really, it’s not like it was a fun experience to go through the .Net -> .Net Core -> .Net, .Net Standard and .Net Core -> .Net years, so maybe Java did it right?
We all know C# exists
There are still lots of folks who love 'protected' and deep hierarchies, of course. There is stuff like spring that uses way too many interfaces with a single implementation, doing something a bit off the regular road ends up implementing tons of the said interfaces anew.
However the 'hate' part is mostly the internet (well esp. Hacker news)warrior topic
That seems absurd to me and I have a hard time understanding it, honestly.
Has the java culture move so far the last decade ?
This is one of Java's big issues. The other is reference equality as a default, pretty horrible. Records help but records are also limited in when they can be used.
https://en.wikipedia.org/wiki/Design_Patterns
Don't take undergraduate OOP courses seriously.
The problem is what if you have a class that needs to derive behavior that's in two (or more) classes? Multi-inheritance is terrible because it becomes a nightmare of which class overrides which.
If there's a shallow level of inheritance (1-2 levels deep), then there's nothing wrong with it. Things like composition for most use cases has been common advice since forever.
What happened with java was that there was a massive movement for enterprise code that way over-complicated everything based on ideas that didn't pan out. There were all these auxiliary patterns, and ideas that people had to learn to be onboarded onto projects, and so many people poorly understood them that it led to even more spaghetti code, too many people that developed using dogma instead of common sense.
The problem is when things change. A simple example - you build a classification of all life and it is built upon the idea that everything "is a" plant or animal. And then one day it turns out there are fungi. This is not a fun situation to deal with and inheritance makes it a lot harder because the "is a" relationship is driving a ton of your logic.
IDK I don't want to get to into it beyond that, many people have written quite a lot on the topic.
There will always be shitty devs, and since java is one of the biggest languages, it definitely has more than some ultra niche academic language no one uses. I don't think it is the fault of the language though, or if that somehow were a reason to choose a different language. Should a decent BMW driver sell their car, because plenty assholes buy that car also?
> Should a decent BMW driver sell their car, because plenty assholes buy that car also?
A better analogy would be "Should you drive on the street where all of the shitty drivers do donuts and street races?"
What are some other well known mantras? The null reference is a billion dollar mistake? Minimise mutability?
Maybe the language designers and library writers could catch up too.
Those are two different ideas.
What else do you want from the ecosystem?
I remember programming Java in Eclipse. And it was powerful for the time, but everytime I read bloated, I automatically think Eclipse since then..
I’m sorry, I’m calling BS here. You can still leak memory in Java.
Every time I see something like this I roll my eyes... C++ doesn't have any "memory problems".
There are sometimes human problems, such as thinking one is capable of coding without understanding the (basic programming) concept of a pointer. But that's because the human's dumb, not a language problem. (This argument also sometimes comes from those who do understand basic programming, but are only familiar with C++98.)
> Thousands of high quality libraries a JVM that is a marvel of engineering after 20 years
I reluctantly have to agree. :)
It can do most anything. It can do it most anywhere. And you can code in it using several different paradigms.
It’s easy to install, it’s easy to be instal several versions, the footprint, by today’s standards, is not enormous.
It’s easy to deploy, especially server side. While you can certainly do all of that modern stuff with containers and what not, it’s not really necessary for Java. Drag and drop a JDK install, anywhere. Plonk your Java server code, anywhere, and point it at your JDK, and. . .that’s it! Fire it up. The JDK is remarkably light on dependencies.
And since most Java apps are “pure Java”, the apps rarely have a dependency outside of the JDK. And since Java apps routinely bundle their Java dependencies, the apps don’t stomp on each other either. Even SQLite bundles it’s binaries in the Java Jar file. So wherever that jar goes (again, and typically bundled with your app), SQLite goes. No floating dependencies.
Desktop bundling requires a bit of search engine research, but it’s doable. And the runtimes can dwarf something like Electron installs.
As a language is Java ELEGANT? Not particularly. It has its own elegance in some areas but that can break down or get overrun in others.
But, boy howdy, it sure is practical. The ecosystem is so huge. It compiles really fast. I marvel at the stories folks tell about their dev cycles in other languages. How do they get anything done, besides sword fighting?
I love Java, but I’m very comfortable in it. But the Maven based dependency system works, it’s huge, it makes use and reuse simple. The IDEs are powerful and mature.
And, finally, Java’s not dead. Hardly. Oracle has been a surprisingly good steward (with warts, as always). The language has been on a rocket of development and shows no sign of slowing down. Server side is still very hot with all the frameworks and all the different approaches. Things like GraalVM taking the JVM in a whole new direction.
And, yea, that. I’ve only been talking Java the language, not the JVM itself per se. That’s a whole other thread.
Dependency injection frameworks are your bread-and-butter in server-side Java, and some can take a while to grok, but Java can be very productive after the chosen framework "clicks" for you. Typically, this means you'll be writing constructors or factory classes that construct your dependencies per request. The way you wire your factories into the system differs by framework, but it often involves using Java's annotation syntax.
Not having to worry about memory management is a huge win for productivity over C++. Likewise, constructors in Java are much more sane than in C++.
Classical object-oriented programming is intuitive, and Java tends towards the "one obvious way to do something" paradigm. I find it pretty easy to hop into legacy code bases, if I already know the framework being used. The collections API in the standard library is one of the best classical OO APIs out there.
The JVM provides great visibility into the performance of your system. A lot of instrumentation comes "for free".
I'm not sure where you get the "dirty" and "bloated" feeling from. By any definition I can think of for those words, Python would be in the same bucket.
you can chose to not use DI frameworks at all..
The language gets updated regularly and is managed very competently. Although it may seem to trail on some aspects vs other langs, it benefits from second mover advantage - new features are done right, for the right reasons.
Any Java code ever written is essentially eternal, both in text and compiled bytecode form. Compiler and VM compatibility guarantees are unmatched. You can stumble upon 20 year old code and just use it.
The platform is mature and robust. The JVM itself is a marvel of engineering.
The ecosystem is extremely rich. There isn't a problem that wasn't addressed by a library or a stack overflow question.
The tooling is unequalled. IDEs can reliably perform large code transformations because there is no preprocessor or macros and the type system is relatively sane.
If you know Java you'll never be out of a job.
Like I academically understand disliking Java but this just makes no sense.
I agree C++ is bloated in a 'just make it a setting' / 'add this feature' / 'kitchen sink' sort of way.
I agree Java is bloated in a boilerplate, empty directories, maybe except for another directory, 'oh god the boilerplate' sort of way.
But if you are talking about the source code itself, then things are quite different.
I'll answer your question with a question: Have you seen https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris... ? :)
I'm guess that to those of us who remember when Java came out, "FizzBuzz: EE" is what we think of when we think of Java. :P
In Java I have to type a bazillion characters to get anything done! And make all these useless directories and files and InterfaceClassFactoryProtocolStreamingSerializer BS. And worry about how that executes.
C++? No bloat*, just speed
*Yes, there's some _optional_ bloat. But compared to Java? no contest.
I can see that being used as a reasonable argument for bloat.
Because there is plenty of bloated and dirty stuff in C++ and Python too.
* Threads that can use more than one core.
* The best approach to package management I've ever seen, no-one worries about typosquatting or "someone already registered all the cool crate names" because every package has at least two coördinates - a group id based on a DNS entry you must be able to prove you have access to, then the package name. So I can publish my malicious package gauva, but as I can't register it with the group id "com.google.guava" or even "com.google.gauva", no-one is going to mistakenly use my package.
* No fucking dance of the C libs. Or Rust these days. I want to use a Python package that wraps a C lib, but there's no binary wheel built for my version of Python and/or platform arch, so build from source it is. Which I'll figure out when pip install -r breaks, and then I get to a) apt-get/brew/etc. install the lib and/or headers b) install any compiler toolchain needed buy not yet present c) export compiler flags and header locations d) all of the above, and then run my pip install again. Until the next library breaks.
* Intersection types for generic bounds. I don't need it that often but when I do, I really miss being to say that for `def foo(ex: T) -> Z`, T must implement interfaces A and B.
* Logging. Occasional horrible security holes aside, Java's logging story is one of the best I've ever worked with, compared to it, Go's sucks, Python sucks more, like how you can't set your preferred timestamp format AND include millis without writing a custom formatter (seriously, go look at the source code of logging.Formatter.formatTime...)
* JMX and MBeans. Having detailed VM performance metrics and the ability to change exposed values or execute an exposed method at runtime built into the VM is amazing. Want to have a quick look at what's happening with the GC? JConsole in. Want to set one logger in particular to DEBUG to see what's going on in prod without having to restart anything? JConsole or jmxterm and away you go. Want to experiment with your batching parameters under prod load to find the sweet spot for throughput? Expose them via an MBean.
* No need for pyenv, virtualenv, etc. Although being fair, if you're working with different incompatible JDK versions, you'll usually end up using SDKMan like Python uses pyenv, and Maven needs a little bit of additional configuration to locate your toolchains, but Gradle is smarter about finding your available JDKs.
* Libraries with type signatures, makes for far easier code reading, and if you pass **kwargs 5 levels down, I hate you. Yes you, sentry-sdk...
* Libraries with correct type signatures, looking at you confluent-kafka, either publish type stubs to typeshed, or update your goddamn doc-string type hints so my IDE doesn't think that confluent_kafka.Message.value() is missing a parameter.
* The ecosystem. I really really really miss the ecosystem of JVM FOSS packages.
* The speed, in general.
I'm sure there's more, but these are the pain points I've been having of late.
Nd it is very compatible with Java itself, interop is dead simple
Other follow fashion and if some dick looking for fame declares A as obsolete and B as the new and shiny one (to be replaced soon anyways) then the lemmings would follow and sing the gospel.
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.
There's as much useless junk in Python's standard library as there is in Java's.
You can choose which report you would prefer: Microsoft's, Google's 65%, ... I'm sure they just hired bad developers that don't understand pointers.
Sure, human problem, but if no human can use the tool correctly, then surely there is some problem with it. And no, that doesn't mean that memory unsafe languages don't have a place, but we really should have a very good reason for going down that road.
If everything is the programmer's fault for being dumb, then Brainfuck is an excellent system language.
Well, every time I see someone claim this, I roll my eyes.
I love Python, and I've been experimenting with Rust and enjoying it so far, but this situation could have been better handled, I think.
I agree 100% about scientific Python though. It's a whole new level of horrible when it comes to dependency management.
You have to do literally none of these things to write working Java.
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
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.
I'm thinking something that would be a clear differentiater such as a multithreaded GUI framework.
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.
Persuading Java devs not to use nulls is hard.
[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...
Nulls are an error in that no language feature solves them (though third-party tooling does), so far at least.
The bias does exist but it's going away, especially that now a lot of developers are moving over to M-series macs.
This is just incorrect use/understanding of Java’s execution model. It does have a runtime, but it is definitely not a VirtualBox VM.
The bytecode interpreter for Java (and other similar languages) is literally called a Virtual Machine, due to the way it functions:
https://en.m.wikipedia.org/wiki/Java_virtual_machine
It would seem your understanding of the Java execution model is flawed.
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?
}
}Remember that OOP and inheritance to some extent came out of the need to develop GUI systems. Inheritance is still heavily used in GUI toolkits because it's a good fit for that problem space. You have graphs of objects that need to be treated at different levels of abstraction, and controls often need to customize (override) or implement some behavior that shouldn't itself be a part of the public API.
Attempts to get rid of inheritance and OOP in UIs end up looking like Compose or React. I found very quickly when working with these that pure composition just wasn't sufficient and these approaches have their own issues; problems that OOP trivially solves become difficult to impossible to solve cleanly without it.
It has features like LINQ. It has routinely had a lot of good updates, new features always coming out, increases in performance, etc. The primary thing lacking has been good support for developing and deploying on Linux, and a couple other devops-related things. It's a shame to see that holding it back, but at least MSFT actually opened up Linux support at all (nobody thought they would).
But you often want the concurrently running threads to return some results, that try-with-resources is part of the structured concurrency that helps you with closing off a branching point, similarly to how we use while loops instead of gotos. `go` in itself corresponds to `goto` basically, with many of the same negatives.
The Java version would require all of those in the exact same way though.
As long as the object signature matches the interface as parameter then you can use it.
1. https://github.com/manifold-systems/manifold/tree/master/man...
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.
But TruffleRuby is something different. It is another Ruby implementation (just like JRuby is a Ruby implementation) using the Truffle framework. And Truffle framework requires to be run on GraalVM.
Long:
Please make your Point Understandable.
Without DI, there's too much flexibility in how objects get constructed. If you want to add new functionality to a legacy code base, it can be difficult to track down the different integration points and slow to plumb through your dependencies. These projects can turn into spaghetti very quickly.
DI solves this with a simple recipe: define your functionality, define your dependencies, wire it up to the injector.
The pattern is useful in all OO languages, Python and C++ included.
I started using just static factories in my code, and abandoned all those DI and it works well enough.
> I started using just static factories in my code, and abandoned all those DI and it works well enough.
i have done this as well (though not in java), it makes knowing what gets initialized how and where much easier and faster to debugInteresting. Can you expand on this, explain more? I honestly don't know what you mean or are referring to -- and I'm a heavy modern c++ advocate -- but suspect if I did it might expand my mindset/viewpoint a bit. :)
I think a lot of people wish it had stayed at say C++7, a mature C superset, but that's enough now. I probably should have said 'I understand' rather than 'I agree' - I don't feel strongly, I don't use either of them.
Yeah, C++ is a living language, I can respectfully see that bothering some people. I viewed c++98->c++11 as essentially a new language with c++14 and c++17 being "bug fixes" for the "new language" that is c++11. But certainly, it can require more learning about every decade now.
I strongly suspect that in a few cases some Java devs using net new systems and avoiding common frameworks will perhaps be able to avoid lots of inheritance but I find it insane to say that that's common or even easy.
which isn't an explanation, but just the same as the OOP mantra that was taught in undergraduate courses.
I'm not sure what vested interest MSFT had in some of the changes introduced for iOS in .NET 8, but now it can be targeted with NativeAOT too, so there is clear investment and dev effort both from community contributors and MSFT employees to support various platforms.
Somewhat arbitrary example, there is also a project to support RISC-V (https://github.com/dotnet/runtime/issues/84834).
Azure is also the big moneymaker, so these days they want all their technologies to have Linux support since that's the dominant cloud OS.
Starting that single though with "The" indicates to me that you don't think those are two different ideas.
Compare:
the idea that inheritance is really good idea and belongs everywhere.
With: the idea that inheritance is really good idea. and belongs everywhere.They actually fixed the billion dollar mistake...
I haven't worked with Kotlin in a while, but IIRC their non-nullable references actually do include runtime checks, so you cannot simply assign null to a non-nullable and have it pass at runtime like you can (easily) do in C#.
If anything I think the cases where you can use Go successfully are only o fraction of the cases where you can use Java successfully. It's not necessarily Java itself but the ubiquity of the JVM and its ecosystem (see Kotlin, see Scala as examplea that have leveraged this ecosystem successfully)
Old languages force you to understand really core issues because the stack is 1m+ lines of code and you need an operating model for all that magic.
New languages do the same thing, but it's because half the really good stuff is <experimental>
Python and JS are in the current sweetspot, go is up next, and after that, Rust.
could you elaborate why do you think so, and maybe give examples of cases where Go can't compete with Java?..
Otoh, Java has a huge ecosystem. Huge. This plus dependency managemnt make it the first choice in most cases. Not even going to go into the massive innertia given that Java has been around for decades (who is going to rewrite everything in go?)
Today most of the time I pick Kotlin which is sort of whatbJava could have been (or maybe will become) with proper investment and care.
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. - Rob Pike
Today though, with languages like js being the most popular one, I would be surprised if inflow of mediocre developers into Java world would be even the same, not to mention higher, then with those more popular languages (Go included)
who produce mediocre code based on the wrong ideas of object oriented programming
This overlooks this history of OOP. Each decade, lots of new ideas have emerged so that skilled programmers continue to use the same languages (Java, C#, C++, C), but change how they use them. C++ in 1996? Let's fight about diamond inheritance! C++ in 2006? C++ in 2016? C++ in 2026? Repeat for all four languages that I mentioned. The story will look similar. Anyone good writing new code in these four languages isn't using many levels of inheritance. It is gone. Sure, it exists in the language for historical reasons, but it is hardly used in new code. "Prefer composition over inheritance."That will be reality for most companies, no matter the language. And if Java projects still got delivered in such conditions - it might also be a mediocre language but it is also just good enough.
The language itself might be fine but I'll never touch it because of that.
It's a great book for Java programmers who want to make the jump to functional-style programming. If that's the transition one is hoping to make, then I would definitely recommend the book.
https://www.manning.com/books/the-well-grounded-java-develop...
I was just pointing out that your critique was about the included batteries, not the language. Every language has some bad libraries in its ecosystem.
Context that isn't documented with every usage is context you need to remember. Trade off, but I'd much rather not have to remember multiple contexts for the same name every few hundred lines.
> Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex.
https://github.com/golang/go/wiki/CodeReviewComments#variabl...
If you're writing a few line script, you don't need a DI container. Once your program gets large, it becomes extremely messy without one. It's no surprise projects like [1] exist.
Any complicated DI solution must have a reason for it being there and I’ve seen too many projects complicating themselves on buzzwords like DI or MSOA without really needing either that much
It only ever runs tasks that actually have to be run, has integration with javac, can work in parallel, and even has cross-company build caches if needed.
Also don't forget that Java can do hot reloads with the debugger, or with tools like JRebel. Certain frameworks support it 100% and it will be much much faster than whatever go does.
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?
Lack of a "default" stack for nearly anything. The stdlib is great but the ecosystem isn't. Which web framework? Does it come with a logger? If not which logger? Do the third party libraries I want to use work with my logger/have a mechanism to provide one via an interface or am I shit out of luck? This goes for so many more things though, cache libraries, data structures (because Go stdlib collections are a joke). Contrast to Java here you have a relatively minimal set of very long-lived deps for all of these "basic" things. Ecosystems like Spring, the slf4 facade, Apache etc provide the foundation that most Java programs sit on and that has no alternative in Go land.
Go module transition was also hot garbage. It's better after sure but going through it was worse than Java 8-9 or to Java 11+ both of which were "difficult" transitions for Java but vastly less disruptive for me personally at least. Then take all of this stuff and multiply it by the number of teams you have if you don't have a central team doing library choices and laying down architecture guidelines - which we eventually got but not before all the Go codebases had turned into by and large unmaintainable messes.
IMO Go is high velocity only in the simplest sense, it's very easy to pump out shit tons of code. With a big team of mediocre developers this is even more true. The problems all come later. Big change in requirements? Good luck with that. Had a team try to go crazy functional with Go and now they have immutable data everywhere and allocations are completely destroying the throughput of the Go GC? Good luck fixing that. etc.
The velocity eventually moves from it's strength to it's achilles heel once the codebases are big and bad they are really hard to fix.
I get that most of these problems are "big company" problems and maybe in smaller teams with a stronger hiring bar you won't run into these or maybe not at the same severity but they severely impacted my view of how well the language scales to large teams and codebases.
I’ve never had a logging issue in large systems. Explicit error return (as you know, on every function) allows you to log in your code, not lean on only libraries that support your interface.
Modules rollout was part of growing up. But, you won’t get Go 1->2 upgrade issues as we have 100% backward compatibility on version upgrades. Moving to the latest version of Go is trivial and simply unlocks new features.
Too many allocations for Go is going to be too many allocations for JVM too. This seems like an problem isolated to that team.
I’ve done Go at both a three engineer startup and at Google and I can’t help but notice none of these are really the type of problem that crop up later.
>IMO Go is high velocity only in the simplest sense, it's very easy to pump out shit tons of code. With a big team of mediocre developers this is even more true. The problems all come later. Big change in requirements? Good luck with that. Had a team try to go crazy functional with Go and now they have immutable data everywhere and allocations are completely destroying the throughput of the Go GC? Good luck fixing that. etc.
I doublt things would go better for teams trying to go full OO in haskell, or full functional in Java.
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.
> What’s a better story for polymorphism than duck typing?
Something that give compiletime guarantees and has not runtime overhead.
> What would (better?) null-safety look like in Go?
https://kotlinlang.org/docs/java-to-kotlin-nullability-guide...
No Option type, so you can still use results of a function if it returns an error.
98k results...
You are making great points for Rust, Ocaml and Haskell.
Thanks to Turing, anything that is Turing-complete can duplicate any other thing that is Turing-complete. So, yes, you can do all of Haskell's types in Java. That is not a flex.
The flex is doing them in a non-horrific way.
https://books.google.co.nz/books?id=Ra9QAAAAMAAJ&q=inheritan...
Why do you think DI is so prevalent in Java codebases? It's a great way of simplifying composition.
The GoF book (Design Patterns) had the same, somewhere early, like in the Introduction, in 1994:
Exactly what I referred to here:
With that said, even the Java EE/Spring complicated word has been moving towards a less inheritance-based future.
Wide inheritance through mixins is common, though.
I don't think that's quite it when Ruby is also from the 90s and figured out not to tie namespace resolution to the directory tree
I like Golang a lot but the standardisation and verbosity in Java-land has some benefits after you get used to it enough to learn to ignore the boilerplate, it's pretty good to have some kind of consistency enforced by the VM when working in large codebases.
You literally ask it to move some class and then it does it. And it's very obvious what it's doing.
Abstractions exist for a reason. In Java, that abstraction is coding in classes and packages, not files of text.
lineCount is literally perfect meanwhile sliceIndex could be anything.
With all due respect, this and similar examples are just plain wrong, and I really can't take anyone seriously when that example is used. The point of programming, and its abstractions is to help you complete a task, and make the implementation maintainable and easy to reason about. I think grabbing the "is a" part is fundamentally bad -- there is no point in creating a taxonomy in and of itself, this is no database for that data. Inheritance sometimes is the correct abstraction and while it is definitely overused, when it's correctly applied there isn't really another abstraction that would fit better. E.g. see a graphics library's Node class as the stereotypical correct application.
1. That my point is bad for some reason
2. That inheritance is sometimes a great tool
We agree on (2). Inheritance is pretty amazing, even if I think that it's ultimately a terrible feature to build so ingrained into a language and to expand in power to such a degree.
As for (1), I don't really get your point. Abstractions help you complete a task - ok. Abstractions are to help you reason about stuff - ok. Something about "is a" being bad? None of that really explains why my example demonstrates the problems you run into when you try to build a classification of values using inheritance. But I also said that I wasn't going to really try to explain much, it's been written about plenty.
Judicious OOP design allows others to change behavior based upon needs that perhaps the original coder never thought of.
I would not call most Java OOP design judicious. The control in Java is owned by the library writers and the language devs -- not the people using it.
In a more flexible type system with union types and other magical features, your example problem would be less of an issue.
However Java has an extremely limited type system so there is no middle ground between composition and inheritance. Once you choose one way, there is no “middle step” to migrate over.
But it is quite advantageous when, as you say, your model is well thought out and stable.
sealed interface Option<T> permits Some<T>, None<T> {
record Some<T>(T value) {}
record None<T>() {}
}
Sure, you have written 3 words more than Rust, and?> I doublt things would go better for teams trying to go full OO in haskell, or full functional in Java.
That is the beauty of Java, no one does this.
They don't feel like they need, should or will get approval to.
Go is pretty much the wild west because "the std lib is enough" attitude permeates everything. Build your own datastructures, build your own anything really.
I can understand why many people find that attractive and hate Java as a result, sometimes Java feels more like Lego and less like programming but it does create predictable reliably constructed software that is generally easy to clean up even if it's done a bit poorly.
Which is something I value because I often end up in the code janitor role and/or being air-dropped in to get a project back on schedule or drastically cut down the defects etc.
Without some specific call-out, it can be assumed that this is a very niche viewpoint that has no bearing on modern development.
The vast majority of projects use inheritance, today.
Does it use Spring? extends SpringBootServletInitializer
Meaningful responses using values from a request? extends OncePerRequestFilter
Formatting exception handling responses on web requests? extends ResponseEntityExceptionHandler
Then there's all the interfaces you have to satisfy, because it's all tied into the standard library and popular libraries.
I don’t like modern Java because there’s too much non-Java magic code. Layers of stuff that “helps” but removes me from the language. Entire programs written in config and special text wrapping classes and methods. How it works requires understanding multiple intersecting languages that happen to be strung together in .java files.
Edit: when something is in config it’s not checked at compilation. Every environment from dev to prod can have its own config so when you compile in dev you don’t know what’ll happen in prod. I know: let’s add more tools and layers.
That's true of the Java compiler, for which the documentation is strewn around the intenet...but an example is here: https://medium.com/javarevisited/compiler-generated-classes-...
The Java syntax doe not require extending Object explicitly.eg This is a valid, useless, class:
public class App {}
> I don’t like modern Java because there’s too much non-Java magic codeIt seems like this is the common path for popular languages. They develop their own library-backed DSL's for the most common use cases, which are often little more than macros (@data @getter, @notnull, etc). I am biased by what I've seen in the last 30 years though.
However Java doesn’t support type union so you can get into some ugly and verbose situations but the JVM doesn’t really check type so this is more a compile-time issue and could be fixed in a future Java language revision.
Isn't the "sealed interface" described in the OP blog post a type union? Or you mean anonymous unions?
I haven’t written Java in a while and I can’t remember if you could sometimes fake a type union using a generic type on a method, but if you can, it’s definitely super ugly and would raise eyebrows during any code review.
Unless by "incomplete" you mean it could have more features. But C++ and Rust (and Java) have been piling on features for years, with no signs that they will ever slow down or stop, proving that they're also "incomplete" by this definition. IMO this is really just a result of there being too many cooks in the kitchen, and a lack of leadership committed to saying "no" to feature requests.
This is also why using Rust or C++ requires every organization to agree on a subset of the language they will utilize. Rust isn't as far along this path, but it's heading in the same direction as C++.
But with Go, every organization can use the complete language. That's how simple it is.
Go has been used by many organizations to build stable, large, and scalable systems that have been operating successfully in production for many years now. That's how complete it is.
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.
After 20 years of building all sorts of software, I know that this a fact, but the challenge is to make others aware of it.
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
High allocation rate feels weird with micro services - I suppose that depends a lot on the coding style. G1GC is meant for generally large setups, with several cores at least. E.g. the default setup of 2048 areas on 2GB heap means, allocations over 1MB require special care.
Except… when we try to build it with Jenkins on a Kubernetes cluster, this happens: https://github.com/oracle/graal/issues/7182
If you want to express your soul in your code, do that on the weekend using whatever language you want. Trying to mix these motivations is a recipe for disaster.
Successful by what metric? Achieving business objectives? I don't necessarily disagree with the point, but this seems like a truism.
The syntax is easy enough you can start coding just after having looked at basic examples.
Personally I actually like Go because it's so boring.
I tend to see languages more like tools rather than valuable knowledge I must learn, so a simple yet powerful enough language is a perfect fit for me.
Not anymore, if you got stuck, by doing that once too often. But 2-3 levels can be allright as well. It depends, what you are doing amd with whom.
No, there is a lot wrong with bad code. No need to blame the language or any programming concept.
Note that I say that for all programming languages, not just Java.
Just to play on the comparison: would you say that a gun is a "dangerously unsafe tool"? I would tend to think that guns have been very well optimized over time, and I don't know of a gun design that prevents me from shooting myself in the foot. Actually that would be a limitation of the gun.
But we all agree that people who use guns need to learn how to use them properly. Why doesn't this apply to programming languages? How did we as an industry end up in a place where it's considered the norm that developers don't really know what they are doing and need some kind of child safety in order to not hurt themselves?
Why is Java itself bad, because some people (maybe) teach it wrong?
Also inheritance as a first lesson with OOP is not bad either, if the follow up is sound. But as far as I know, the concept composition > inheritance was already taught 15 years ago.
It's very easy to persuade the customers of your language that everything that goes wrong is their fault and not yours. I rejected that...
- Tony Hoare
I am fine with requiring that professionals know their tools, even if I'm well aware that this is not the norm.
All you really need for that is named procedures. A way to combine modules and different files certainly helps too.
Type hierarchies can even hinder code reuse, because you can’t just pick the stuff you actually need.
Java obviously isn’t thread safe like Rust is, but it’s typically safer than C++ on that front too.
It's up to the programmer to verify the safety of code using unsafe. But it they do so correctly then you can rely on the borrow checker to verify that everything else is safe.
This means that when you run into a thread safety bug in Rust code you should only have to look at the unsafe blocks to find the culprit.
while (true) { list.add(1); }
Here you are, which language won't leak memory here?
Also, which language will let you connect to a prod instance without a performance hit to get some stats on the heap and its allocated objects? Hell, you can even list every instance of a type as I've recently learned.
> while (true) { list.add(1); }
idk, I don't think endlessly growing memory usage is really what a leak is, a leak is really when you have no way of accessing the allocated memory to free it (ex. dropping the last remaining pointer to a `alloc`'d block in C) or the opposite in garbage collected languages: accidentally holding onto a strong reference to objects that should be freed.
So basically what I gave an easy example of. Sure, it won’t look like this in practice, you probably accidentally keep adding to a list, or a cache, but basically this is what happens. The former kind of leak can’t happen with tracing GCs.
The OP is complaining about the difficulty of the API because it is _exposed_ through inheritance.
The complaint about `SpringBootServletInitializer`, for example, is exactly this. There's nothing wrong with inheritance. In fact, SpringBootServletInitializer is exactly what you want to use inheritance for - because you need to build your app using the servlet api.
There are http servers that aren't servlet api, such as https://vertx.io/docs/vertx-web/java/#_re_cap_on_vert_x_core... , which uses little to no inheritance (since the api surface is smaller).
In java, race conditions can enter illegal application state, but their scope is much more limited. NPEs are 100% safely handled, etc. you can only get off the safe road with using Unsafe unsafe, and manipulating the JVM’s inner state which is not even allowed by default. Depending on application, this difference may matter a lot!
Though I still don't see a standard web stack. "stdlib is enough" is only true for people that don't need their hand held and/or can organise good standards across teams.
If it was just me or people I know are good writing the software that works. At Google it probably works because of aforementioned hiring bar. At the average large-ish company? Not something I like leaving to chance because I have usually ended up disappointed.
No, Go’s GC is a toy compared to the JVM’s. It is lowerish in latency by actually stopping the application threads when under high contention.
Java doesn’t slow down the allocation rate, it tries to keep up with the churn.
The GC monoculture is great from a simplicity and out of the box experience but there is a very good reason to a) want multiple different GCs tuned for different workload types and b) have competition so that the best designs can be found rather than having to fight to be the only implementation.
Java's GC improvement is relentless because Java's applications are relentless in memory allocation.
Being on endless Java memory/perf issue prod calls I can say Java GC improvement, performance tuning is cottage industry in itself. Meanwhile end users keep suffering but at least Java devs get to tune GC to their heart's content.
In the course of optimizing I came to know the various JVM GC algos (concurrent mark/sweep, parallel old, etc) by the corresponding memory graph alone. I never, ever had to debug similar latency in the Go stack.
Both of those are only picked for low sized heaps with few cores, probably within a container. Were these micro services?
G1 is the default for larger heaps and multiple cores, and ZGC and Shenandoah (low latency GCs) have to be manually turned on AFAIK.
OP said:
>Java doesn’t slow down the allocation rate, it tries to keep up with the churn.
This is incorrect. ZGC will block a thread when it cannot give a thread any memory, because it can't collect and free memory at the pace needed. Google "allocation stall" for this. ZGC can achieve very low latencies akin to Go's GC, I don't know if the throughput is higher or not. Multiple cores and some GiB of heap space is when ZGC will shine.
The general principle is to design in as much safety as possible without compromising the intended purpose of the tool too much. The degree to which this is possible varies. Inherently complex and/or dangerous tools like guns, cars and aeroplanes require significant training before safe operation is possible. Most tools however can be made perfectly safe to use for anyone without any special training. Like plugs and outlets [2].
Programming languages are no different. The history of PL design is defined by the ever increasing restrictions placed on what languages let programmers do in the pursuit of safety & correctness.
But really, we humans, especially programmers, have no clue what we're doing and need all the help we can get.
[1]: https://en.wikipedia.org/wiki/Safety_(firearms)
[2]: https://youtu.be/139Q61ty4C0?t=42, https://youtu.be/139Q61ty4C0?t=105, https://youtu.be/139Q61ty4C0?t=205
There's nothing wrong with this at all, say:
class MyClass:
def __init__(self, my_dep1=None):
if my_dep1 is None:
self.my_dep1 = get_my_dep1() # Or potentially raise
else:
self.my_dep1 = my_dep1
[...]
Then to test: def test_my_class():
mocked_dep1 = MagicMock()
my_class = MyClass(my_dep1=mocked_dep1)
[...]I know pytest does DI with fixtures, and trying to figure out what you're pulling into a test can be difficult.
"turn X upside down" are different words for "use X contrary to its original intention and design".
You will find very few people willing to let go what people undestand as Java so as to be able to do Haskell-in-Java.
It’s not like Java hasn’t been going in the same direction, see records, sum types, pattern matching.
Lets compare with Java. Java has forced everyone for decades to shoehorn everything into classes (sometimes an enum, sometimes an abstract class, whatever). Last time I checked it was still impossible to simply open a file, put a function (!) inside and be done. No, Java forces you to wrap that into a class or similar construct, as if a function in itself was not enough and not self-sufficient. There is a whole mindset behind this, that seems to come from an ideological "everything must be a class, because then I can instantiate and then I haz objects and can call methods". Other languages don't need classes to have objects.
Decades fast-forward. Java learns, that lambda expressions are a nice idea. Java will offer structs. Java learns, that lightweight processes are very neat to have. And despite all that, the old footguns still remain and could only be undone at significant cost, because of backward compatibility. This is where programming language design sins really rear their head. PHP suffers from the same problem. Horrible standard library, but cannot be fixed, unless you break backward compatibility.
On one hand you can state, that it is all on the programming, who is "holding it wrong" or needs more education. On the other hand, the programmer can choose a better designed tool, that doesn't cut their fingers every time they try to use it. Just because it is possible to do a good job with Java, that does not mean, that Java is a good tool for the job. It means you can only let the most experienced people work with the tool, instead of what we have now, every Billy knowing Java, writing classes and getting a kick out of inheriting from a super class.
Good teaching will avoid weighing things one should avoid disproportional. There is no good reason to teach inheritance early on. It should be a thing taught on the side, something one quickly glances at and says: "Yeah, that also exists, but lets not get into that much, as we will not need it much ..."
But in Java you probably can't avoid it for long, because you will want to make use of some library sooner or later and library authors might force you to make some class inheriting from their library's class, define a behavior and pass that in. But often that is not enough ... No no no, you need to pass in a factory for classes, that implement an interface, and their methods will implement the actual thing. I have seen this recently for logic of checking, whether a password is valid/acceptable. Why the heck do I need to implement a factory for that, when the actual task is simplest logic, checking whether the password has all required kinds of characters in it?
Usually this is completely overblown, because you want to pass in some behavior, that could be expressed as a simple function. I shouldn't need to create some brimborium. All I should be required to do should be to implement the logic in a function and pass that as an argument to the library.
The existing ecosystem forces its "OOP" on you. If you are not willing to throw out decades of ecosystem, which actually is the main advantage of the JVM, then you will need to deal with the cruft that has been created before.
But the concept of inheritance I like. And I use it succesful allmost daily. And like I said, I am aware of how you can use it in the wrong way. And my code surely is not perfect either, but the flaws I have, do not come from inheritance. Those parts are actually very clear, solid and stable. And still flexible.
And Rust is trendy I know. I have never used so far, so it was news to me, that they don't even have inheritance, but to convince me, that Rust has the superior concept, I would like to see it to be used as widely as inheritance languages first.
The user doesn't write DI code for Django Class Based views. E.g. the view doesn't accept a Database upon instantiation.
People still hate it because it's immutable, therefore you can't do hashmap.add(), and it's hard because they can't randomly return nulls, and it's slow because the hashmap now takes o(log n) always instead of o(1) sometimes and o(n) other times.
People hate it because it's different to the Java they learnt 20 years ago, that they claim is exactly the same as today.
There was a talk at PyCon a while back about that patterns commonly used in Java were non-existent in Python.
Why isn't it fast out of the box? Why does the simple "provide a list of deps, provide a list of paths, build" take so darn long? Because Gradle is a great tool or something?
Don't forget that it's also already at version 8, where each change is mostly incompatible, bizarre and inexplicable breakages between versions, often due to meaningless option renames. Imagine if they spent all that effort on actually making it a great tool.
Async/await also splits the standard library and the ecosystem in 2 (blocking vs non-blocking, or blue vs red), and it can't automatically update the behavior of old code.
The introduction of virtual threads in Java also works well with "structured concurrency", as seen with Kotlin's Coroutines. Kotlin's approach to concurrency is also superior to that of C#, actually. But what's interesting about Java is that its evolution is often one that involves runtime changes, lifting all boats. Java engineers preferred pushing more changes in the runtime, and somewhat ironically, the JVM ended up being the true multi-language runtime.
Java is a good case study of how languages should evolve. It has extreme backwards compatibility, and features being pushed are assessed for how they impact the whole ecosystem, including libraries or languages not in Oracle's control. Project Loom was developed in the open, compared to what Microsoft usually does.
(if you want to take a look at good structured concurrency, you might be interested in Swift implementation)
Set<File> s = new HashSet<File>();
s.add( new File( "c:/temp" ) );
s.add( new File( "c:/temp" ) );
System.out.println( s.size() );
C#, prints 2: ISet<FileInfo> s = new HashSet<FileInfo>();
s.Add( new FileInfo( "c:/temp" ) );
s.Add( new FileInfo( "c:/temp" ) );
Console.WriteLine( s.Count );
C#, test fails: string path = "/tmp/filename.txt";
FileInfo f = new FileInfo( path );
File.CreateText( path ).Dispose();
Assert.True( f.Exists );
f.Delete();
Assert.False( f.Exists );
https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...Languages have their strengths and weaknesses.
Also, async-await is not a good thing — virtual threads are superior in every way in case of a managed language.
It appears every six months there must be new language features being added.
The last one, for declaring fixed size arrays in structs, with an annotation instead of proper grammar change is getting ridiculous.
type erasure for generics is annoying
I too have spent significant time programming in both C# and Java. This complaint about type erasure in Java: When does it affect your daily life? There are so many craft workarounds available now that it hardly comes up anymore.Also, would your opinion of Java significantly change if type erasure was removed or never existed?
Adopting async isn't impossible at all, there is very little demand for it.
Many uncancellable threads + mutability + goroutine panic kill it all are serious issues for golang.
With valhalla (value types), it might just come!
Frankly, Id rather have a result and optional/nullable type like in rust/kotlin than deal with exceptions in any capacity.
The correct way to deal with Java’s checked exceptions would have been introducing a Result type, or, preferably, type algebra, like in TypeScript, so something like:
fun openFile(fileName: String): File | FileNotFoundException {…}
Then you could handle it similarly to null: val content: String | FileNotFoundException = openFile(“myfile.txt”).?read()
…then you have to do a check before you use content as a String…
or val content: String = openFile(“myfile.txt”)?.read() ?: “File not found”
(There could also be other possible ways of handling the exception, like return on exception, jump to a handling block, and so on.)In fact, null should be treated as an exceptional value the same way all exceptions should be.
I do not know about go ecosystem, but java and spring have mature solutions that cover the most advanced use cases.
But that's the thing with Java, yes there are libraries for everything and one could see that as a problem actually.
Spring was mentioned before, but it's the perfect example of an overengineered/ heavy library that does a lot of black magic.
With regard to this:
> But most enterprises that have a non-software focus generally prefer languages that have been around for a while and are known by lots of people.
I know developers who work for large blue chip financial sector and other traditional sectors. They’re on the same React/Vue/Webpack/whatever treadmill as all the frontend devs devs working at web dev agencies. And they’re often compiling it down from TypeScript, which has not been around for a very long while at all and is not known by lots of people (relative to the size of the JS community.
Swinging a bit of Go for a service in an environment like that isn’t really that hard.
You know that Go has generics since a couple of years now?
I think it is a worse choice, it's too ingrained in the language and ecosystem to change. That and a couple of other nits would likely make me pick Java over C# or Go if I was starting a new project going forward.
C# isn't flawless. Java isn't flawless.
There are a lot of languages you can use with the JVM, Closure is one of them, Groovy, Scala and Kotlin are some more. Kotlin is gaining a lot of traction, especially since it’s backed by JetBrains.
Having to deal with errors and forcing the developer to do proper error handling is a good thing.
Exceptions are not composable, cannot be generic, and it is not visible in the source code which lines can throw, so every line is a potential branching point.
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.
However, the Go code bases I’ve encountered have been less abstract and to the point(even if it means repetition of some little things). I also found it to be free of boilerplate except for the if err != nil part.
I guess those that have spent decades in such abstractions must have learnt a skill to navigate such a spaghetti. But, I still hate it every time.
No matter the amount of abstraction though, one rarely runs into the sort of issues in Java that Go code tend to run into frequently - causing multi-million dollar mistakes frequently even for experienced Go programmers. For loop semantics https://bugzilla.mozilla.org/show_bug.cgi?id=1619047, Unintended variable shadowing, common mistakes in slice appends/copies, slices and memory leaks, defers inside loops, nil interface vs nil, panics in go-routines. There are loads of bugs in OSS projects wrt to these usually repeated again and again.
Go is very simple to learn and very hard to master writing bug-free code. Looking forward to seeing how languages like Rust perform if/when adopted by enterprise.
Even if you’re shipping a jar. You can ship one artifact by using jlink or you ship the runtime in the docker image.
This has been a solved issue for ages.
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.
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
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.
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)?
https://conveyor.hydraulic.dev/
It can be used to ship servers, we use it that way for our own servers. It sets up systemd so you don't need to use Docker. But where it shines is desktop apps.
Windows users get a little 400kb EXE that installs/updates your app and then immediately starts it without user interaction required, so it's effectively a single EXE. Mac users get a bundle. Linux, well, Linux users get a deb that sets up the app+its apt repo, or a tarball. Maybe in future a FlatPak or Snap or something else.
It knows how to bundle and minimize a JDK along with your app as part of the release process. Works great for desktop apps.
It's for Electron too, same deal, easier than Forge in my humble and very biased opinion.
Sorry for the snark, but users will undoubtedly be very grateful to them for not considering Electron.
Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.
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.
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.
Are .war files no longer a thing? ;)
So in Go you make available the library's source code, and the end-user will download it at build time, or vendor it in the source tree.
Having AOT compilers as commercial offerings, was seen as one way to capture value in the Java market, in a culture where most compilers were still commercial, GCC being the exception.
In an anemic way, with mostly third party offerings few people know or care about, and various degrees of pain and shortcoming to their use. It should be a first class feature, and as simple to use as is in Go (including for cross compiling).
In general, if something exists in "some form" for 30+ years in a language, and only a handful of people use it, whereas in another it's used all the time and people from other languages are jealous of how well it works, then the formers "some form" is not a good one.
These days there's also GraalVM native image which does produce Go-like results. But with everyone using Docker on the server anyway it doesn't matter anymore. People who talk about single binaries are confusing to me. What are you doing where shipping one file is so much simpler than shipping a container?
Desktop applications.
Java would be a more popular desktop application platform if it weren't for the difficulty in this area (which, to be fair, isn't the only difficulty - cross platform is difficult inherently).
Not really.
Same with your second point. I made a big point to explicitly say I think that right now Dart is very underused and has a huge potential outside of Flutter. Quoting that back to me as though it was something I hadn’t considered is equally as confusing.
And its null handling and checking is the worst.
I absolutely hate it. Java over Dart any day.
I’ve never heard this criticism before and have no idea what you’re even referring to here but I am genuinely curious..
Just the other day I ran across an issue where spring was wiring things up correctly on Linux but not Windows.
The pagination object is bulky and unnecessarily complex, where a simple offset/limit is enough (and a nextUrl for cursor-based access).
When we looked into cluster locks, they’re not even released if one node goes down. I mean, who would need a lock implementation that just stores a line in a DB? And the doc doesn’t even warn about it.
Apache contributors were much better-skilled.
Apache contributors were much better-skilled.
That is quite a broad statement. Two negative points about Apache Java libraries I can think of: The original "lang" libraries have not aged well at all. Also: The HTTP client libs are a fiasco. Very challenging APIs and weak documentation.The async/await syntax works with the language's other statements, but for a long time it had gotchas. It doesn't qualify as "structured concurrency", and it has the aforementioned issues — it's (accidentally) error-prone, it splits the ecosystem in two, and has no interruption model.
I am not familiar with Swift, but I think you can hardly beat Kotlin's implementation. This is a good introduction from Kotlin's lead:
Interruption is achieved through cancellation tokens and has to be handled by consuming methods. There is no way around it because interrupting execution at an arbitrary point would lead to all kinds of issues regardless of the language (unless it implements some form of transaction abstraction and rollbacks all uncommitted changes).
In Java, you don't need to initialize those "tokens" manually, because the interruption signal is baked into Threads. Moreover, a lot of the standard library cooperates with Java's interruption, which is why you see plenty of methods throwing `InterruptedException`; and it's also reflected in types such as `CancellableFuture` or the `Flow.Subscription` (reactive streams). Of course, user-level code that isn't well-behaved, can end up catching InterruptedException, or resetting the interruption signal, without actually interrupting. This makes Java's interruption model somewhat error-prone, but it's workable, and at least it's baked in.
Note that interruptions could also be preemptive, as you don't necessarily need cooperation. If you think of the call-stack, or flatMap/SelectMany in reactive APIs, the compiled code could check the interruption flag automatically and interrupt the call chain.
And resource leaks aren't necessarily a problem, if the interruption protocol is well-thought-out. In Java, try/finally still works in the presence of interruption, since at worst you get an `InterruptedException`. It's not ideal because you can interrupt the interruption process, and in truth the ideal would be for interruption to be its own communication channel, complementing that of exceptions. But it's totally doable, and here I am familiar with several libraries from Scala's ecosystem, namely Cats-Effect, Monix, and ZIO that show it (with limitations imposed by the runtime).
Either way, what C# provides is basically next to nothing. In fairness, some C# libraries tried fixing it, such as Rx.NET, but it's not enough. And the aggregate result in the .NET ecosystem is that interruption is not something people design for. Like what to interrupt a network socket? This ends up being a setting, presented as a timeout in case of inactivity, as a configuration of the connection, instead of a higher-level generic function that can be applied on the consumer side. And the probability for resource leaks goes up actually, because in the presence of concurrent races, you really need interruption.
That’s a choice, but it is unfortunate for the programmer. The truly right way to do it all is like Erlang does - where all processes are cancellable and nothing bad happens.
async fn read_file(filename):
f = await os.open(filename)
let data = f.read()
await f.close()
return data
this is equivalent code with green threads: fn read_file(filename):
f = os.open(filename)
let data = f.read()
f.close()
return data
As you can see, async/await is pure syntactic noise, it doesn't convey any important meaning.Better implementations offer eager execution and allow to easily interleave multiple concurrent futures/tasks, like C#. Green threads on the other hand are a workaround to deal with blocking for the most trivial case of cooperative multi-tasking, offering little beyond that.
It's not really a type, otherwise it would be something like Future<T> in Java and plenty other languages. It is usually implemented as a transformation to a state machine.
Also, Loom is M:N and calling them green threads doesn't give you the whole picture at all. Not exactly sure what you mean by easily interleave -- functionality wise the two is more or less equivalent. You just get to keep your simpler mental model (and tooling) with virtual threads.
> eager execution, interleave multiple concurrent tasks
green threads give you that for free
> deal with blocking
blocking is an implementation detail; both async/await and green threads can be implemented on top of either traditional blocking IO, or callback/non-blocking IO
I'd be even happier to discuss concrete code blocks / examples, that's where the superiority of green threads truly shines
Also DNS uses dynamic linking unless configured otherwise, while being OS specific, then that are the libraries that rely on cgo.
Let me tell you the most common scenario. I have a fairly popular FOSS CLI tool written in Python. I cannot use the features from the latest version of Python or else I will alienate ~50% of the userbase. This problem does not exist with Rust or Go.
This problem would have existed for Java on Android except Google took the burden of "desugaring" the Java 17 code -> Java 8 compatible bytecode for the old devices.
Then I moved to Kotlin + Jetpack Compose, and I don't see the point of Dart anymore.
uh-huh
but, in all seriousness: I wonder why that is? I struggle to think of a technical reason that go would be unable to load and invoke a .dll even if one had to name it .so https://github.com/golang/go/blob/go1.21.1/src/plugin/plugin...
1. Start IntelliJ and check it out. It's Swing but it feels modern and fresh.
2. Go to https://www.jfx-central.com/ ... JavaFX was introduced years ago to add far better support for modern visuals. JFX Central is a website written with JavaFX itself - it runs server side and streams drawing instructions to the client (implemented using a mix of divs, svg tags and css). You can do this because JavaFX implements the CSS2 drawing model. The website looks modern, like any slick website would today, but you can also download and run it locally. It uses Conveyor to do that in fact. The desktop version looks exactly the same as the web version.
But yeah, arguably they should be unified.
a little memory heavy, but no more than electron apps while being less janky
>Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.
Like Slack having 1000x the memory and CPU use for 1/10th of the features a 200K app like ICQ used to have 25 years ago?
I've used Electron as a developer, too. While it may have been fast & easy at one time, I think that argument is losing merit. Electron had a lot of security holes and patching them has made common tasks harder. Security with Electron is a problem in general given its massive surface area.
Users definitely notice the performance issues. It's certainly not just devs complaining about abstract issues. Yes, people use these tools, but that's mostly out of necessity, not preference. Moreover, big players like VS Code have had to write swathes of the app in C++ for performance reasons.
I look forward to a return to optimizing for the user, not developer velocity. There will likely always be attempts at cross-platform toolkits and there may be a good solution in that space. I hope Electron isn't the best we can do.
I'm new to Electron development. I've read the docs recommending hardened runtime. Would it be sufficient for security? Can you give examples of such security issues?
I'm not suggesting every Electron app is a giant bag of vulnerabilities, just that you have a lot more to keep on than you would writing with GTK or UIKit. And, since everything is bundled with the application, you can't rely on OS updates to fix things for you. You need to cut a new release and distribute it.
If you follow the Electron recommendations on security you'll be on the right path. You'll just find common tasks have become harder than they were back when Electron was attracting people with its ease-of-use. I found trying to do type-safe IPC to be an exercise in frustration. If you read the old docs, tutorials, or books, you'll find IPC used to be considerably more free-wheeling. Locking it down is the right trade-off, I think. But there's been an accumulation of many small changes like that. As a result, I don't think the framework is nearly as easy to work with as when Slack or VS Code adopted it.
> Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.
So you are saying that it is bad for the user (bulky and slow), but good for the developer productivity. I really don't see how the users could like the fact that the developers are being more productive while making a worse app.
And then Electron happened.
Which is worse for users, but better for developer productivity (probably nicer for developers too: C++ is not exactly fun).
I am still hoping that JVM Desktop apps come back at some point, maybe with Kotlin Multiplatform?
But I found out that there is this new Preload module in electron that lets you use Node.js very easily via normal export and import. I'm using a popular starter template and it works great so far.