Don't think like Java is a good example of that, more like the anti-example. Plenty of more sophisticated languages move way faster without negative effects on long term progress (C#, Kotlin, to some extent Scala)
> A well designed feature becomes easy to extend. A poorly designed one is easy to extend initially, but becomes far more difficult in the long run.
Most of Java problems come down to unfortunate design choices in the past. E.g. checked exceptions vs lambdas
Funnily enough, your example has actually supported my point.
They just released a proposal that effectively side-steps the problem of checked-exceptions-in-lambdas. Here is a link -- https://inside.java/2023/12/15/switch-case-effect/
This is still just a proposal, so nothing is hammered out. And we can talk about the merits of this solution. But my point is, by focusing on designing features the way they have, you can minimize intrusion while maximizing the value provided. I think this proposal demonstrates that extremely well.
This proposal allows the developer to maintain all the good parts about checked exceptions and lambdas, while being able to side-step the major downside.
> Don't think like Java is a good example of that, more like the anti-example. Plenty of more sophisticated languages move way faster without negative effects on long term progress (C#, Kotlin, to some extent Scala)
To be clear, when I say long term, I am measuring on the scale of decades. In that respect, I actually don't think your statement is correct.
To give an example with Kotlin, someone (I think Brian) talked about Kotlin's release strategy for features being great for short term, but costly in the long term. Kotlin had record-esque classes long before Java ever got theirs, but now that Java finally has theirs, Kotlin is using an annotation to disambiguate between Java style records and Kotlin style records -- https://kotlinlang.org/docs/jvm-records.html#declare-records.... While this isn't some critical problem on its own, this type of rift growing wider means that Kotlin is going to have to wrestle with how much they want to allow Java representation of concepts to exist in Kotlin. For example, how will Kotlin handle Java's version of pattern-matching vs their own? The 2 are not the same, so you cannot just call them aliases of each other - they have different semantics.
To give another example with C#, there has been a lot of recent discussion about finding potential alternatives to their async-await concurrency model. They cite the level of effort it takes to maintain the async await style code and the costs that come from this. Rust had a similar conversation not too long ago too.
The point I am trying to say is that, I think Java's strategy of play it slow and conservative is actually paying off for them, but on the scale of decades. Some of the languages it gets compared too aren't even that old yet.
The main problem of checked exception in lambdas is that Java's type system does not allow expressing statements like "`forEach` throws whatever the lambda throws". Here are two links:
http://james-iry.blogspot.com/2012/02/checked-exceptions-mig...
https://blog.jooq.org/javas-checked-exceptions-are-just-weir...
The proposed syntax puts the exceptions on equal footing with the happy path. Not a bad idea, but irrelevant to the main problem.
This is fine but removes only about 10% of the pain of mandatory exception handling inside typical lambda.
Nothing short of making exceptions transparent to lambda receivers will cut it for me.
> Kotlin is using an annotation to disambiguate between Java style records and Kotlin style records
This is inherent risk of building on top of something you don't control. Don't really see this as fair comparison. It's a known design constraint. The only way to avoid it is to not build on top of Java or not adding any features on top of Java.
> To give another example with C#, there has been a lot of recent discussion about finding potential alternatives to their async-await concurrency model. They cite the level of effort it takes to maintain the async await style code and the costs that come from this.
I had a very different take-away. They did PoC with virtual threads and decided it's not worth the switch now and async-await that they have is good enough.
https://github.com/dotnet/runtimelab/issues/2398
> Some of the languages it gets compared too aren't even that old yet.
C# is more than old enough to drink and Scala just had its 20th birthday this weekend :) Java is only 4 years older than C#.
If your wish is to have Checked Exceptions be throwable from the lambda, then I understand your point. Still, I stand by argument, which is that they are actively addressing these pain points, but doing so slowly in ways that minimize intrusion.
> Kotlin
If it's not a fair comparison in your eyes, I'll leave it.
> async await in C#
I'd be more willing to accept your point of view if there wasn't so much discussion online about how painful async await is via color-my-function. And that's ignoring the fact that languages such as Go and Erlang have been garnering a big following, in large part due to their concurrency model and how simple it is.
I have had my share of async await too. I think it's no coincidence that many language designers are looking more and more into moving away from async await, thus prompting these PoC's and experiments.
As for the C# link specifically, I interpreted that to be that they felt the pain of async await strongly, and did the PoC in response. However, the PoC did not play well with their async await world, and trying to change the world to make room for it would be too much of a lift. Therefore, they stick with async. I did not interpret it to be that async await was good enough for them, but rather, that improving async await is the best option available to them for now.
> Age of languages
Sure, there are some languages that are a similar age to Java. But those are the languages where I believe the costs for the decisions they made are becoming more apparent. I already gave 1 example with C#. And I think we can both agree that Scala's design philosophy served as a great learning experience for other languages, but was a poor survivability tactic as a language. It feels like the language is buckling under its own weight.
Either way, I am not trying to say that these other languages are bad. Merely that their design philosophies benefit them in the short term, but show cracks in the long term. Java's philosophy, meanwhile, minimizes intrusion, and thus, allows it to sidestep alot of the problems that these other languages are trying to get out of.
Also moving fast and breaking things newly adopted .NET culture is the reason I am mostly stuck on .NET Framework for corporate production apps, unless we're doing newly written microservices, and some big names like Sitecore decided it was easier to buy products written in other languages than rewrite their stuff into .NET Core.
Just an idea - maybe you can use CodeQL?
The transition to .NET core is indeed rather painful but it's just Microsoft's choice. Things were dropped/backwards compatibility broken mostly to simplify runtime but I saw nothing that couldn't have been emulated with some modest effort.
Also they could've easily softened the transition by continuing development of .NET Standard a bit longer and allowing side by side hosting of 4.x and Core in same process as that seemed to be the plan in the beginning. I guess they don't care.
.NET Standard existed and still exists as a bridge for .NET Framework and "odd" platforms like UWP (effectively dead), Unity and maybe a couple of others - you never know! Fortunately it slowly but surely fades into obscurity as all the actively developed platforms are converging around pure 'net6+'-based TFMs.
Special note regarding "being stuck with .NET Framework" - this may be true for some enterprise scenarios but I have grown to adopt a mental model of considering opinions like these as nonsensical or deeply out of touch with reality as much more useful way of dealing with audience that still lives in this camp. It is not worth entertaining, nor it is reasonable to respond to "move fast and break things" criticism after all these years - some people just have a bone to pick, for the pace is "moderate" and there are no changes made like in the early days of .NET Core 1.x and 2.x when the platform's fate was uncertain.