Java 20 / JDK 20: General Availability
mail.openjdk.org
mail.openjdk.org
case Tuner t && guitar.isInTune() -> ...;
and case Tuner t if guitar.isInTune() -> ...;
both seem as clear as case Tuner t when guitar.isInTune() -> ...;
...am I misunderstanding the reasoning here? Is it being introduced to keep the grammar definition simpler?1: https://foojay.io/today/its-java-20-release-day-heres-whats-...
In the case of "if", you're right, the grammar doesn't work, both because the syntax of Java already says that the if condition must be enclosed in parentheses and also because an if block is a statement and you need an expression here.
To make either of those work, you'd have to make the rest of the pattern matching syntax much worse.
At 3rd preview, they switched to when: https://openjdk.org/jeps/427
In that JEP they state:
> Based upon experience and feedback we propose instead to allow when clauses in switch blocks to specify guards to pattern labels
So I assume people thought && was confusing.
The “if” one does not suffer from any such problem and reads okay to me. I guess one argument against it could be that since the whole thing is a distinct grammatical construct, why not just introduce a distinct keyword instead of making an existing keyword depending on context? That’s mostly a question of style though (e.g. I don’t know right now if Java already has an habit of reusing keywords like that).
And don't even get me started on `static` in C++.
Given this pattern matching syntax change, you'd write:
switch(obj) {
case Tuner t when guitar.isInTune() -> ...;
...
}
For a switch, but for an if statement it's written as: if (obj instanceof Tuner t && guitar.isInTune()) {
...
}
Edit: I do wonder, actually, if avoiding "&&" is to allow the "when" case execution to be reordered (e.g. to allow JIT to extract common "when" conditions prior to the switch expression), which would be wrong to do given the short-circuiting rule implications of "&&"FWIW, though, I don't find it "bizarre". While it's not a boolean expression per Java, at least in my mind it serves the same purpose: "in the case that thing x is of type SomeType and ...some other thing about ((SomeType)x) is true...".
I think it's a tough choice, I'm sure they were probably not wanting to add a new keyword, but at the end of the day "when" is so familiar to anyone who's ever written SQL (which I'm guessing is most Java devs) so seems like a good choice.
But if some construct looks the same, but is not the same, and worse, only "somewhat" the same, you get confusing behavior. At least for newcomers, or even just software engineers who just don't care about language grammar that deeply (which I imagine are not only a lot, but also a lot of the Java target audience specifically).
Case in point, the author I was replying to wondered in a followup whether && would/should still act like a shortcut-and operator would, or not.
There is definitely some subjectivity to it, but personally I much prefer if different constructs look clearly different. Any ambiguity is squashed immediately, and in the case of "when" it's still fully clear how it works. I'm also okay with the "if" variant, because while it reuses a keyword, it's clearly a different "if".
https://en.wikipedia.org/wiki/Java_version_history
The non-LTS releases are every 6 months and have support for one year.
LTS releases are every 4th release which falls on odd numbered years in September.
Not exactly, they are supported for 6 months (well, till the next JDK release) at least the builds on jdk.java.net page. Other vendors might provide different support.
Azul for example supports some releases (13 and 15) for 3.5 or 2.5 years (MTS)
And now I’ll get 21 soon? Awesome.
type when expression -> block
if expression -> block
For parsing works better, for programmers is better since they don't confuse both concepts, and in general pattern matching isn't the same as a if condition or a switch.https://stackoverflow.com/questions/199918/explaining-patter...
I think the difference is when you start pattern matching on tuples and other types that you cannot do a simple `if ident.isinstanceof(type) ->`.
For example in Elixir you can do:
def ident(param = {:key => true}):
This will only match when the param type is not only `map`, but also contains a key named `:key` and its value is `true`.In the end though, it's syntax sugar (but everything is if you put it that way classes are syntax sugar).
Our main reasoning was:
"&&" is intuitive but it means that you can have a pattern that is immediately followed by an infix operator. That can be problematic if you ever want to make "&&" a valid pattern infix operator. And, in our case, we ended up doing exactly that, so "&&" would have been ambiguous. If you were to write:
case foo && true:
Then it could be parsed as either a pattern that matches when the value is equal to the constant "foo" and is equal to the constant "true". Or it could be parsed as a pattern that matches when the value is equal to the constant "foo" followed by a pointless guard that always succeeds."if" is nice because it's already a reserved word and the semantics are pretty obvious. But in Dart, an if statement always has the condition in parentheses. Those are pointless in a pattern since we already have another explicit delimiter separating the guard from the case body:
case foo if (condition):
// ^
We could say that the if condition in a guard doesn't need parentheses but if conditions elsewhere do. But that's likely just an annoying footgun where users will write them unnecessarily (but harmelessly at least) in guards and forget them in if statements and get compile errors.If Dart didn't require parentheses around if conditions, we probably would have used "if" for guard clauses to (like Scala and Rust do).
So we tried "when" and most users and team members seem to like it. Syntax design is a human-centered process so often the right answer is just what feels right to the most people.
[1]: https://github.com/dart-lang/language/blob/master/accepted/f...
Syntax design is weird. Sometimes the only way to tell if I did it right is when no one says anything. People complained when I used "if". No one did after I switched it to "when". <shrug>
The flip side is that it also bugs me when language / library designers make weird choices that don’t seem to have a compelling reason (or at least the compelling reason for the difference doesn’t outweigh the cost of being different).
No win situation ultimately except to figure out who has good language tastes and weight their feedback more, but that’s subjective and something people try to avoid, ignoring that they’ve already done this by virtue of limiting who the experts are working on it in the first place.
Familiarity and intuitive structure in a low context language is very important. A language syntax is for humans to comprehend. If a new syntax is under discussion, it's probably around new functionality (or sugar). Where is "encourages innovation" in the list of reasons to use any specific syntax? I would say, far down on the list.
You mention about the human-centred nature of syntax design... do you have any instinct for why one route vs another felt right to users? Do you feel like you've developed a better instinct for this over time, or is it still hard to predict what will feel natural to users?
That's a good question. It is something I spend a lot of time thinking about when I see how users react to a design. In this case, I don't think I have a good answer as to why "when" seemed to go down easier than "if".
> Do you feel like you've developed a better instinct for this over time, or is it still hard to predict what will feel natural to users?
I'd like to believe so, but "if" was my first pick, so I guess not. :)
I think what our team does have now that really helps is better processes to evaluate a design, talk about it, get feedback from users, and incorporate that feedback into the design. It's all pretty informal, but I think we're iteratively able to get designs users seem to like.
But it's always really hard. There are so many trade-offs and users have different preferences and expectations, so finding the right balance is always difficult. I think that's why it's so endlessly fascinating to me: you can never fully "solve" syntax design.
var favoriteTask = obj switch
{
Developer dev when dev.YearOfBirth == 1980 => $"{dev.FirstName} listens to metal",
Developer dev => $"{dev.FirstName} writes code",
Manager _ => "Create meetings",
_ => "Do what objects do",
};The other day I did something similar for cell biology. As a layman I wanted to learn more about DNA, mRNA, tRNA, translation, transcription, mitosis, miosis, etc. I think I learned it much better by querying Chat GPT than say reading Wikipedia. Something about the interaction, to ask for more details on what was not clear that helped me understand it.
Trained on data up to September 2021.
Wrap the ChatGPT API in a ReAct pattern implementation where one of its available actions is a search against the Java 20 docs?
Probably better for reference than tutorial, though.
For example, I asked ChatGPT about the meaning of a non-existing verb ("to spoink"). It originally said the word didn't exist, but when I said "are you sure? we talked about it before, it's something to do with trifle" it invented an entire episode of the IT Crowd (Season 3, Episode 3), talked at length about how that word appeared twice. It claimed that one character - Moss - created it as the sound a metal ball would make if it hit a hard surface, and also that it's what happens when you leave a trifle out uncovered in the open and it goes lumpy. It's creative, but it's nonsense.
Very few humans could pull this off convincingly or would even attempt it. They would say "no we didn't talk about the verb 'to spoink' that sounds like nonsense".
My wife, a physician, reported similar errors when I got her to ask it medical questions.
Using chatgpt to learn about something new is flat out dangerous.
> The Byzantine Generals Problem is a more difficult problem than the Two Generals Problem because it requires a consensus algorithm that can tolerate the presence of faulty actors. There are many solutions to the Byzantine Generals Problem, including Byzantine Fault Tolerance, which is a common technique used in distributed systems to ensure that the system can continue to operate correctly even in the presence of faulty actors.
One can't jump over a chasm, you have to literately bridge from the known to the unknown.
https://privatebin.net/?45ed3f39de71f289#eyiwC81q93MtfpimZBH...
I then ask it so summarize and ELI15. If ChatGPT is feeding you bogus knowledge then you are already consuming bogus knowledge over your existing channels. If you aren't including feedback into your understanding you are recording, not learning.
- https://github.com/wesleyegberto/java-new-features (terse, includes links to JEPs, good jumping off point)
- https://github.com/winterbe/java8-tutorial (quick tour through features of Java 8)
- https://winterbe.com/posts/2018/09/24/java-11-tutorial/ (same for Java 11)
Books:
- Java 8 in Action / Modern Java in Action (Raoul-Gabriel Urma, Alan Mycroft, Mario Fusco; 2014 and 2018 respectively)
- The Well-Grounded Java Developer (Martijn Verburg, Benjamin Evans, Jason Clark; 2022) - not specifically focused on new features but does cover them in the context of going deeper into Java and the JVM.
- https://www.baeldung.com/new-java-9,
- https://www.baeldung.com/java-10-overview,
- https://www.baeldung.com/java-11-new-features,
- https://www.baeldung.com/java-12-new-features,
- https://www.baeldung.com/java-13-new-features,
- https://www.baeldung.com/java-14-new-features,
- https://www.baeldung.com/java-15-new,
- https://www.baeldung.com/java-16-new-features,
- https://www.baeldung.com/java-17-new-features,
- https://www.developer.com/java/java-18-features/,
- https://www.infoworld.com/article/3676699/jdk-20-the-new-fea...
- https://www.infoworld.com/article/3653331/jdk-19-the-new-fea...,
I think what you probably had in mind is that C libraries of the 90s that did M:N threading didn't turn blocking operations into non-blocking?
Using blocking operations to switch contexts is really nothing new. Heck, the cooperative multi-tasking systems of the 80s (Mac, Amiga) all essentially did that for processes (not threads), and so did Unix in the 70s.
Yes, mostly, though my history knowledge is definitely lacking so do correct me if I’m wrong.
But you are right, there was nothing fundamentally missing, probably just no good OS support for non-blocking IO calls in the early days? Though probably the IO-CPU ratio was also different, so the benefits were not as big?
sorry, but Haskell has had it way before Go, with proper STM too. Neither Erlang nor Go are offering the same level of ergonomics for compile-time checked M:N threading.
I'm not very into this Loom virtual threads thing, but... what's the difference between this automagically conversion of blocking into non-blocking in a M:N model and a 1:1 one? I mean, couldn't the same be done with normal threads too?
Loom implements every IO on top of a more modern async OS calls, and these virtual thread context switches are on the order of function calls, so the overhead and number of switches that can happen are much much lower.
Note that this advantage obviously goes away the moment you call into native code. Then the JVM is in the same position as the kernel. It doesn't control the compiler or the stack any more, and so that's why a virtual thread becomes "pinned" at that point and you lose the efficiency (the JVM needs to acquire more kernel threads). Fortunately though the JVM ecosystem doesn't rely on native code all that much, so it should be rare in practice.
This advantage can be brought to other non-Java languages too via Truffle. Truffle languages are reimplemented on top of Java and when programs call into native code they have the option of calling into JIT compiled LLVM bitcode instead of real native code (or indeed any JVM bytecode library). In that situation the JVM remains in control and so things should in theory still be Loom-able. Not sure if that's currently true in practice, but it could be.
Forgive me for staying doubtful, but I recall hearing this same "the JVM can be very fast and efficient because its JIT has complete knowledge and control" spiel back in the 90s, and back then, anyone could clearly see that the JVM was not as fast compared to pre-compiled native code as it was being promised.
> The kernel can't do any of these things - it has to assume a process is a black box that could do anything with its stacks, could be compiled by anything and so on.
The kernel has to assume nothing; it can dictate how userspace processes behave. As an example, a process which plays too many games with its stacks, without kernel cooperation, will quickly find out that signals share the same stack unless the kernel is told to use an alternate stack. A process which uses a register declared in the platform ABI as being for kernel use will find out that it can be unpredictably overwritten on a context switch. There are things like shadow stacks and segment register bases which can only be manipulated when the kernel allows it. And so on.
Of course, for compatibility reasons, the current ABI allows userspace processes to do a lot of unpredictable things, but nothing prevents a new "highly scalable threads" process ABI, with stricter rules, from being developed if necessary. Or it could be that only a few cooperative additions to the userspace to kernel ABI are necessary; we already have things like the many options to the clone() system calls, the futex system call, restartable sequences, etc.
Well head-for-head Java will still lose to C++ in many benchmarks, but that's not really due to compiled code quality, it's more about language semantics. Java is very fast for the sort of language it currently is. The big wins for C++ are that Java doesn't have value types or support for vector operations. Both are under development, actually vector ops is basically done but it's waiting for support for value types (see discussion elsewhere).
Also GCd languages trend towards a functional style without much in-place mutation, whereas C++ trends in the opposite direction, so C++ will sometimes use the CPU cache more effectively just due to prevailing habits amongst programmers.
> The kernel has to assume nothing; it can dictate how userspace processes behave.
Yes in theory you could fuse the language VM with the kernel and research operating systems like MSR Singularity did that. But a normal kernel like NT, Linux or Darwin can't do this and not only for backwards compatibility. The JVM will do things like move a virtual thread stack back and forth from the garbage collected heap and do so on the fly. Unless the kernel contains a JIT compiler, GC and injects lots of runtime code into the app's process it's going to find it tricky to do the same. By the time you've done the same you haven't implemented better kernel threads, you've made the JVM run in the kernel.
Will calling a coroutine do zero heap allocations like async in Rust?
> with the ease-of-use experience of threads
That's highly subjective. Threads usually require locking which is often hard to get performant and correct at the same time. Async/await allows to write concurrent code with no synchronization.
Well you still need some sort of synchronization, because an "await" allows arbitrary other actions to occur. If an await is introduced in code you transitively call then you might find that some invariant you were expecting to hold has now changed across a call when it previously didn't. Fundamentally, locks are about making invariants atomic and that's independent of exactly how code is scheduled and when.
Hmm,I don't see how async/awaits makes a difference. Care to explain?
Like, if you have multiple sources that can add or read from a queue, unless there is a single thread running all your async loops (ala python), you still need some synchronization. At least that's my experience using coroutines heavily in kotlin.
let mut buffer = ... // create buffer for holding data
let mut input: TcpStream = ... // connect to remote endpoint
let mut output: TcpStream = ... // connect to remote endpoint
loop {
select! {
_ = input.readable() => {
input.try_read(&mut buffer)?;
}
_ = output.writable(), if !buffer.is_empty() {
output.try_write(&mut buffer)?;
}
}
}
The mutable buffer is shared between the part that reads from input and the part that writes to output, and reads/writes happen concurrently and independently. Yet there is no explicit locking anywhere!Synchronization is achieved implicitly by the fact that sequential code executes in only one place at once, so when it executes the reading branch, it does not execute the writing branch. You can apply exactly same reasoning as with any single-threaded, sequential code.
You cannot model this easily with threads. If it was a single thread with blocking I/O, then it could block forever in one branch and stop reacting to events on other branches. If it were multiple threads, then they would somehow need to synchronize accesses explicitly to the shared buffer.
Basically a leaky abstraction, you have to think whether your code will block or not.
Go went through the same process, before eventually figuring out that they do need to make their lightweight threads (goroutines) preemptive.
When you're in a "tight loop" (e.g. a matrix multiplication, which is basically 3 nested loops that only load data, do math, write data), Java's virtual threads just won't yield. So if you write your app in the "wrong" way, you lose concurrency.
There's a lot of discussion about this from the Go side. The original issue was this one: runtime: tight loops should be preemptible https://github.com/golang/go/issues/10958
> it's possible to write a tight loop (e.g., a numerical kernel or a spin on an atomic) with no calls or allocation that arbitrarily delays preemption. This can result in arbitrarily long pause times as the GC waits for all goroutines to stop.
The proposed (and ultimately accepted, AFAIK) solution is described here: https://github.com/golang/go/issues/24543
> has put significant effort into prototyping cooperative preemption points in loops, which is one way to solve this problem. However, even sophisticated approaches to this led to unacceptable slow-downs in tight loops (where slow-downs are generally least acceptable).
> I propose that the Go implementation switch to non-cooperative preemption using stack and register maps at (essentially) every instruction. This would allow goroutines to be preempted without explicit preemption checks. This approach will solve the problem of delayed preemption with zero run-time overhead and have side benefits for debugger function calls
I 100% expect Java will have to do through the same evolution. But first they'll probably try to deny reality for a few years. Funny enough, same as has happened with Go and generics.
VertX framework had such a sentinel, but migrating code from the async futures to a normal threadpool can be a bit tedious if your design is poor.
This is incredibly useful: having a good VM to run your code in, with good modern garbage collectors, is not an obvious thing (as many other languages have learned).
This is not the LTS release, so I won't be switching to it, but I'm looking forward to the next LTS.
You may want to look at Blazor Webassembly (https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blaz...) and Blazor United (https://devblogs.microsoft.com/dotnet/asp-net-core-updates-i...)
They scrapped the old CLR and started over with a new "CLR Core".
Then they ported the new CLR Core along with the framework BCL to webassembly and has the CLR running in the browser, upon which they built Blazor Webassembly.
So you can now target the CLR and have your code run in the browser.
And now they are making progress on Blazor Unified where components can start of as server-side rendered exclusively and transparently and automatically move to webassembly rendering within the same application or page. It really is crazy stuff.
Typescript is not the only thing going on in MS Engineering.
As far as I know, Java offers no way to mark objects as stack-allocated, but C# does. Spans in C# allow programmers to produce subarrays without copying. Enums in C# are stack-allocated, unlike Java. So on, so forth. None of this is a huge deal for Java since some of the best GCs in the world are implemented atop the JVM. But I do think C# offers its workarounds when GC performance gets in the way.
The way I have seen it described somewhere: C# has a slightly higher performance ceiling, but a naive application may very well run faster in Java.
What you describe is the result of different philosophies/priorities. CLR focuses on static compile-time optimization, while the JVM is a highly dynamic construct with unmatched runtime analysis. In the 90s, there was a hope that with sufficient escape analysis, the need for user-defined primitives would vanish, which is why value types have not been done prior.
By themselves, accessing values via stack and not by reference is technically trivial. The problem lies in backporting that kind of stuff.
Maybe, it might also be the results of bad design decisions.
> highly dynamic construct with unmatched runtime analysis
As someone who spend quite a bit of time working on custom optimization around hotspot, i failed to see how anyone can describe the current state of the JVM ( J9 is a bit better) as unmatched. V8 and some some extend Julialang have much strong dynamic analysis.
> CLR focuses on static compile-time optimization
Sources ?
Most assuredly not. Back in the 90s, the cost of loading memory and performing a CPU instruction was essentially equal. Today, fetching data from RAM takes 100x longer than a CPU instruction. This makes locality of data absolutely crucial and is a consequence of computing throughput increasing, but latency remaining stagnant (think of it like a database transaction).
With focus on garbage collection, it made sense to throw everything into the heap and use runtime analysis to inline as much as possible. Nobody has forseen how such hardware fundamentals would change over the next decades.
As far as I'm concerned, the proposed JVM spec for value types is the most promising model I have seen anywhere. Instead of a binary choice between entities in the heap and values on the stack, you have a more granular control with incremental benefits and constraints.
> As someone who spend quite a bit of time working on custom optimization around hotspot, i failed to see how anyone can describe the current state of the JVM ( J9 is a bit better) as unmatched. V8 and some some extend Julialang have much strong dynamic analysis.
Didn't know that, probably worth looking into. Though, I wonder how much you can compare V8 and the JVM, given the fundamental difference between a static and dynamic language.
> CLR focuses on static compile-time optimization
I thought that was common knowledge. I mean, does the latest CLR perform any significant amount of runtime optimizations? From what I've read the CLR makes sue of cpu-specific instructions such as SIMD, but no cache/layout optimizations or any inlining during runtime.
https://learn.microsoft.com/en-us/archive/blogs/davidnotario...
In addition if you want to use these new Java libraries in case and Clojure does not catch up with new Java features the ergonomics of using Java libraries with clojure decrease.
And still they are trying to figure out how Ifn Clojure interface with Java functional interfaces.
Also when Project Loom lands on JVM it will benefit Clojure too, allowing to remove code for instance of Clojure futures.
If Clojure catch up with Value classes can increase performance of Clojure too.
But this disregard of Java features or improvements is the kind of Clojure developer so content of what he have that forgots that can get better things.
As someone else noted in this comment section, a lot of useful Clojure libraries are wrappers over Java libraries. So improvements to Java used in these libraries are good for you, too.
I'm sure it works for OpenJDK though.
What other way would be possible, how would you solve it?
Announcement: https://inside.java/2023/03/21/the-arrival-of-java-20/
VM improvements: https://tschatzl.github.io/2023/03/14/jdk20-g1-parallel-gc-c...
Non-JEP Improvements: https://nipafx.dev/java-20-guide/
Better to be right then rushed though.
The odd thing is that part of Valhalla is about how to migrate existing types to being value types, and they already have code that can simulate the same restrictions. So it's not really clear why these features have to wait. Migration is a part of the Valhalla plan anyway.
And as a side note: LSPs are not exactly free these days anymore. Java is not considering it core project (I guess outsourced to RedHat and Eclipse) and Microsoft has a very erratic behavior towards what is in and out the LSP or DAP.
Admittedly I couldn't figure out exactly what repo is being used for Java's syntax highlighting since vendor/README.md says Java is using tree-sitter/tree-sitter-java and grammar.js in that repo appeears to already include references to record.
languages.yml [1] shows ace_mode java, and ace editor's java_highlight_rules.js doesn't mention record anywhere. I'm not clear if github is using ace editor or code mirror, since both are mentioned there.
Hopefully this helps get started on improving the poor syntax highlighting experience.
[0] https://github.com/github/linguist/blob/master/CONTRIBUTING....
[1] https://github.com/github/linguist/blob/c34f887f48d81e2ed42b...
[2] https://github.com/ajaxorg/ace/blob/a2e89b94b4dcdff28bf3c8f4...
I wonder how long it will be before they realize they are in the slow, sad, decline regardless of how many features get added.
There is quite a lot of legacy software that uses Java though.
But:
1. The enterprise users aren't spending their time chasing java versions, and probably already gave up and have a support contract with Azul or someone, or just stick with an unsupported versions.
This is why all the cloud vendors, for example, either have such deals, or provide their own guarantee of support.
So they almost certainly do not care about Java 20 anytime soon unless they are in the weird category of "serious java shop" (rather than legacy java shop), which is quickly shrinking. That is not a big enough class that people will "try to keep up" to frame it in the java-centric version used by GP.
2. The non-enterprise users are also a shrinking base.
And if you consider the history of Java as a language since the 1990's, it has historically had very slow feature releases. If you review the release history of Java, almost half of major relases and changes to the language came after 2014+. var in Java came out in 2018, record was introduced out in 2020 - feaures that were copied from other languages that already had them for years. Java as a language is still is playing catchup here.
It's a feature, not a bug.
Literally the only feature preventing Typescript from being my perfect language.
I'm not sure if it's stalled or not. But it's being discussed.
"We didn't do ..." is so incredibly important.
But I definitely agree with the sentiment - every new feature is another thing that a developer needs to learn if they encounter that code. There should be a large "barrier to entry" for new features.
Not to mention JS is not typed, but pattern matching that can't match typescript types would be terrible.
Trying to do simple things like matching the content of a variable will fail if you forget you have to use a dotted path if you want to avoid binding.
Trying to do advanced things such as "match a dict with variable key names and values, but you wish to enforce the number and the types and unpack them in variables" is ridiculously twisted to do, when it's even possible.
I use match/case, but it like the early years of type hints or asyncio: terrible ergonomics, and we know there is a ceiling to how much it can improve.
Either way, the benefit has to be enormous. It's a feature that can easily break existing code.
You could say “return match…”
It’s not easy to introduce without breakage. Not saying it cannot be done.
But PHP didn’t manage to for example. It also isn’t real pattern matching but that’s a different issue.
Java and C# show how the syntax can be done in a fully backwards-compatible manner. It looks like it's just not the JS way by choice - apparently every new keyword that was added since ES5 is an actual keyword reserved in all contexts, not context-dependent?
// Given an Option
val maybeThing: Option[String] = getThing()
// Classic
if (maybeThing.isDefined) {
useThing(maybeThing.get)
} else {
NotFoundResponseEtc()
}
// Pattern matching (not too IDE auto generated exhaustive match cases!)
maybeThing match {
case Some(thing) => useThing(thing)
case None => NotFoundResponseEtc()
}
This scales well when matching a higher cardinality of things like a variety of Exceptions or enums or other tuple responses like Either etc.What convinced me of the power of pattern matching was seeing a red-black binary tree being implemented effortlessly in Ocaml (I think), while in C++ and Java it was a really difficult algorithm to implement.
When you have provably exhaustive pattern matching (i.e. the compiler forces you to handle every possible case), certain things that are very difficult to write otherwise become very easy.
a = b + c
d = e - f
g = a * d
But the compiler doesn't allow: g = (b + c) * (e - f)
You have expressions that produce, but they don't compose. You can't produce a value from a more complex, nested expression. We, rightly, no longer use languages like that.Pattern matching parallels that, except for assignment and decomposing values. Many languages today let you write:
topLeft = rect.topLeft.x;
left = topLeft.x;
top = topLeft.y;
bottomRight = rect.bottomRight;
right = bottomRight.x;
bottom = bottomRight.y;
Or even: left = rect.topLeft.x;
top = rect.topLeft.y;
right = rect.bottomRight.x;
bottom = rect.bottomRight.y;
(Because at least you can compose expressions on the RHS.) But they don't let you write: (topLeft, bottomRight) = rect;
(left, top) = topLeft;
(right, bottom) = bottomRight;
Or even: ((left, top), (right, bottom)) = rect;
Pattern matching gives you that. It is freely composable destructuring.Also, the "matching" part means that in many languages you can also ask questions about values as you destructure them, which enables a particularly nice style of programming.
I started implementing Lox with Java's sealed classes + pattern matching on switch. The exhaustiveness has been really nice to ensure I cover each new token/expression as I add them.
It's one of those things that when you get used to, you wonder why other languages don't implement it.
[1] https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rd...
I remember the excitement from the different alpha/beta versions that were coming out in 1994/1995.
I kind of assume it's due to the cliché Java-oriented hatred, but curious to hear opinions...
Announcement: https://inside.java/2023/03/21/the-arrival-of-java-20/
VM improvements: https://tschatzl.github.io/2023/03/14/jdk20-g1-parallel-gc-c...
Non-JEP Improvements: https://nipafx.dev/java-20-guide/
"JDK 20 reached General Availability on 21 March 2022"
Should be 21 March 2023
Not sure who can edit that page but putting here.
Object obj = 123L;
String formatted = switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
Unless I'm not understanding something, the default statement should be `obj.toString()` as `o` isn't defined.The Billion Dollar Mistake meets the Power of Compounding Interest to produce an inflation adjusted number that just keeps on giving
See [1] for more.
https://docs.oracle.com/en/java/javase/19/language/preview-l...
with a slight addendum here: https://openjdk.org/jeps/8300604
and podcast that discusses both: https://inside.java/2023/03/21/podcast-030/
When a backend needs an internal UI here, it's always a Javascript app in a browser.
- ScopedValue (Incubator). Seems like a replacement for ThreadLocal that is intended to be a bit less dangerous (it is notorious for leaking memory and file handles). The Kotlin equivalent would be CoroutineScope. I'd say the latter is the cleaner solution. And probably ScopedValues came into existence for the same reason (co-routines & structured concurrency kind of breaking ThreadLocal a bit).
- Record Patterns (Second Preview). This looks a bit like Kotlin's smart casts. Useful. I think the Kotlin implementation with contracts is a bit more powerful and broadly applicable in more use cases. But nice of course.
- Pattern Matching for switch (Fourth Preview). That looks to me like an attempt to beef up the Java switch, which is a welcome change. The Kotlin equivalent would be when. Not a whole lot of difference at first glance. But combined with smart casts, Kotlin is quite nice already IMHO. Scala developers might disagree about a thing or two here..
- Foreign Function & Memory API (Second Preview). Looks like a nice JVM level feature for integrating native code that is potentially also of use to the Kotlin language developers.
- Virtual Threads (Second Preview). The second iteration of Loom. The JVM level implementation for this is going to make Kotlin's co-routines potentially even nicer. I don't think it should be that disruptive for Kotlin developers already using co-routines but performance increases are nice.
- Structured Concurrency. Also Loom related. From what I've seen of Loom so far, it should just work with your existing code without too much changes. And co-routines are definitely a nice API to do structured concurrency so not particularly relevant for Kotlin users. But it's going to be a big enabler for Java developers that have lacked this.
- Vector API (Fifth Incubator). Another JVM level optimization that the Kotlin developers should be able to make use of. I imagine projects like the kotlindl framework (a deep learning framework) can make use of this. A bit niche but nice if you use things like that.
So, nice incremental change and a few nice things that Kotlin will benefit from probably. Whether you use Java or Kotlin, it's going to be nicer for everyone once this ships in an LTS jdk.
- Record Patterns are stronger than Kotlins smart cast because of nested patterns
- Kotlin Coroutines works exclusively with Structured Concurrency already. The JEP just adds a way to use Structured Concurrency with threads (it can also be used in Kotlin if you use pure Threads(virtual or not), but I don't see any reason not to use Coroutines
.NET is loosing people to the "cool languages" in regards to younger generations.
Or if you mean addition of new features, then Java is very conservative on that front and it is still a very small language compared to most other mainstream ones.
Clojure the language isn't missing anything I need.
Does anyone know the solution?
Joining into a synchronous function is an anti-pattern in C# because you consume threads. This seems to be the same, except with the assumption that virtual threads are fine.... But that's not an assumption that can be made by the method writer, right?
In fact, if you feel like doing some advanced stuff, you can write your own schedule algorithm.
It's linked in the email.
That's another huge effort which delivered us an imperfect abstraction.
I would prefer to have HKTs and typeclasses, so I may implement my own IO monad.
I'm not a Java developer, and I haven't even read about higher kinded types or typeclasses (the theory side of programming is my weakness); but you picked my curiosity with this.
Can you elaborate how concurrency done this way would look in Java?
> Almost everything there
Everything. But the mere presence of "equivalents" doesn't make pure functional programming less valuable.
My view has been that reinventing imperative programming (IO monad) on math (pure FP) on imperative programming (the machine) brings me no benefit.
What does a coarse-grained effect type get you? The hard part of concurrent programming is concurrency, rather than knowing what code is effectful.
For monofunctors: reliable error handling, an ability to re-interpret the same IO structure multiple times, better reasoning during refactorings due to referential transparency.
For bifunctors: the above plus explicit domain (expected) error encoding and even more reliable error handling.
i understand that virtual threads are very similar to goroutines in golang
A company that is head-in-sand deliberately not upgrading from Java 8 is one thing. A company that is just being conservative and intentional about the upgrade path, that's another entirely different thing.
I don't mind a company that has been using Java so much that it's just a slow process for them to get on the next thing. Old libraries that need to be recompiled for Java modules, etc. Hopefully it happens for you soon.
For existing software, sure. But the problem I guess is when there are a lot of newer projects starting and everyone is stuck on the older version
The bigger problem is that a lot of places are stuck on Oracle JDK - 8u252 is the last free version so a lot of places just decided they'd never upgrade, nor do they want to look at whether Temurin or Coretto would work for them (the answer is usually yes).
It isn't at all, in my opinion. Consider the introduction of the module system, for example.
Some libraries moved out of the core language.
Custom libraries have, unfortunately over the years, picked up the bad habits (by forking/following public libraries) relying on reflection and packages that shouldn't be directly used (sun.* packages, etc.). So companies are wedged in because they made the poor decision to rely on private packages. And now, Java 11+ enforces this by default.
There are open source Java libraries today that don't run unless you "add-exports" to basically everything. google-java-format comes to mind, because it's using an internal java code parser from the JDK, for example.
But yes, maybe not a good reason to completely block an upgrade. It's just postponing the pain, though.
This is not true for many applications. Due to the removal of many APIs from the JDK with Java 9, I needed the following dependency artifactIds to be able to move a JEE application with SOAP web services to Java 11: jaxb-api, jaxb-core, jaxb-runtime, istack-commons-runtime, jboss-jaxws-api_2.2_spec, glassfish-corba-omgapi, jboss-annotations-api_1.2_spec, activation, jboss-saaj-api_1.3_spec, saaj-impl, stax-ex, jsr181-api, txw2.
Many of these spec API/implementations are provided by different artifacts that are incompatible with each other. Some I only discovered when something failed at runtime as they perform implementation lookups and you don't get compile errors.
Additionally, many of the Maven plugins we used no longer worked and our application server failed to start.
We could fork it and remove the interface or switch to ULID[1] instead.
[0] https://github.com/stephenc/eaio-uuid/blob/master/src/main/j...
Given that interface is trivial, you could also just define it in your codebase. I've done that a few times for shimming small bits of log4j and Spring that some library uses, when i would rather not have those as a dependency.
Unless you’re targeting MSBuild Visual Studio Solutions and Windows only, C++17 is currently the most up to date, stable, and battle-proven version.
I work with C daily and I'm well aware of its many shortcomings (e.g. its huge list of undefined behavior and its PDP11-centric view of modern architectures), but with some effort I believe it could function as a semi-portable second-level intermediate representation. Nim uses it as such, IIRC.
Compiling to C first would introduce some of its own issues, for sure, but I imagine doing so would alleviate the pressure the C++ standards committee puts on compiler vendors each time they expand the size of the kitchen sink.
Which items from this table are lying about GCC v11 support of C++20? [1] Which of the features are buggy in GCC v11-12?
[1] https://en.cppreference.com/w/cpp/compiler_support#cpp20
Just don't use it for new projects on modern hardware. Be on at least the current LTS version for that and enjoy a much more expressive language on a much better JVM.
I'd still upgrade for the new VM's better performance/security patches or whatever, but I don't need any language changes.
Taking this comment in good faith, the following language features from 9+ are incredibly useful for everyday programmers, you should give them a serious try before dismissing them:
- `var` local type inference
- record classes
- text blocks
- switch expressions
- sealed classes and interfaces
Records are nearly useless to me. Immutability is great, but I need a way to derive new sets of information based upon an set of information. And the only way to do that is bug prone.
Text blocks are nice for writing SQL queries and other multi-line things. But I'm not sure how often I actually use it.
Switch expressions are nice because it gets me compile time checking for things I would previously use a runtime check for. Other than that, as they currently exist, they're meh.
Sealed classes are something I've not had a use for. Maybe libraries will eventually make good use of them.
So I would say there's some nice QOL things in here. But I think "incredibly useful" is overselling it.
The thing is, lots of these things are built to support a longer term roadmap towards better support for data oriented programming. I think that is a worthwhile goal to drive for - and the sum of the parts (many of which are still in preview) will be less than the whole together - but we don't have the whole, yet. That, to me, would pass the "incredibly useful" bar.
Regarding records, you never had someone update a POJO, add a field, and forget to update equals and hashCode?
Sealed classes are great for everything parsing/validation, in data modelling. They're not a solution for behavior polimorphism, but I don't think they were supposed to.
New features done right improve simplicity by abstracting away complexity or need to reinvent the wheel. For example Go devs having wrote their own list .map() functions is absurd.
Not exactly tied to the JVM, but IIRC one of the prerequisites for the Spring4Shell vulnerability was the existence of a new method added by Java 9. If you were still on Java 8, you were not affected.
There should be no reason why someone deliberately writes code for a logging library to go fetch and execute code over the internet, and make that the default behavior. The fact that someone did that, and it got approved and published to production, should be an indicator for anyone competent to stay away from Java completely. There is plenty of other compiled languages out there for whatever use case you need.
(For Java 9/10/11 in particular, there are several small enhancements, but nothing as earth-shaking as the changes we got with Java 7/8.)
And even in the unlikely event you don't see any improvement with recent versions of the runtime, something like Spring Boot 3 requiring Java 17+ isn't exactly "invisible" in the Java world.
7 didn't introduce anything earth-shaking (just try-with-resources), lambdas were earth-shaking, just like records and switch expressions are.
I'm not sure that it's inherently insecure, there is just a ton of old stuff in the ecosystem still chugging away.
Or licensing, or having to rewrite too much of those apps for no perceived value. Who knows.
As an example, idiomatic Java style is moving away from getters/setters and instead favors builders and immutable types. That whole inheritance vs. composition concept is changing the way Java is being used.
Meanwhile, C# has baked getters/setters so deep into their language (as properties) that there's just no moving off of them. Have you tried making a Builder in C# - ugh. Much harder than it should be.
C# took the best of Java and ran deep into a cave with it. Meanwhile, Joshua Bloch's Effective Java has completely changed the way that "good" Java should be written and likely helped kickstart the whole revolution that is modern Java.
I agree with your sentiment in general. C# / CLR are truly remarkable engineering feats. But Java is starting to break away from its old molds and is seemingly adding agility as it goes.
As far as ownership. Java is not owned by Oracle any more than C# is not owned by Microsoft. They are both open languages with a majority backing supporter that yes, has a lot of influence over the languages. I'd argue Microsoft has more influence over C# / CLR than Oracle does on Java / JVM (but that's too close to call and probably too opinionated to suggest).
If Java records could be told to have a private constructor, I'd be completely satisfied with them. I just don't like the ability for callers to be able to directly instantiate a record without having gone through my builder to do so. I want to completely enforce that my record is instantiated with all its invariants dealt with properly. A builder is a very nice way of doing that.
My solution was to create a sealed interface that permitted the None and Some records as the only classes to implement it. Those records are not available outside the package, while the interface is exposed. Using default methods in the interface I could expose a state "create()" method which would then instantiate the appropriate None or Some record. In this way you control the exposure of the construction of the specific record implementations of your interface.
You can then either interact with the option through the methods on the interface, .isOk(), .unwrap(), etc, etc, or with the upgraded pattern matching in switches with this release you could have something like
switch(option) {
case option when option.isNone() -> blah
case option when option.isSome() -> foo
}Its not as pleasing as Rust matching directly on Some and None, but it gets you pretty close.
https://news.ycombinator.com/item?id=35133670
This is absolutely possible in Java and the only less-than-ideal part is the generic type having to be specified in the None case (but a trivial method fixes that as well)
This can be used just like rust and similar languages:
switch (option) {
case Some(var x) -> println(x);
case None -> // TODO
}
Hell, you can just further pattern match inside Some, like `Some(Point(var x, var y))`I also remember a discussion in the mailing list about withers: https://mail.openjdk.org/pipermail/amber-spec-experts/2020-M...
Not sure where it stands now, you can ask in the mailing list about this
Such validation logic can be enforced in the record's compact constructor.
And I don't like having to provide one constructor for every optional (default) value, when its omitted. There's just not as nice of a style. The "telescoping" constructor pattern is just really hard to use, read and maintain.
With the Builder pattern, you can easily define complex relationships for your class inner state. Like maybe if A & B are specified, then C shouldn't be specified. But if C is specified, then D should have a default value. Etc.
These types of initialization requirements are not easily duplicated with C# init-only construction. Instead, in C# you have to rely on the callers to know exactly how to correctly instantiate your class, including all the complex logic like in the example above.
So yes, C# init-only setters are kind of the best option you have. You can still create a C# FooBuilder for a Foo class. But generally C# programmers want to go out of their way to directly instantiate a Foo, and the number of bugs you get from this is crazy. It's a culture thing, honestly, not as much a language problem.
My two cents.
https://blogs.oracle.com/javamagazine/post/exploring-joshua-...
If so, is there a reason to go with OpenJDK over Oracle other than GPL purity?
Again, legit question, no axes to grind.
Also, absolutely objectively Microsoft has orders of magnitude more control over C# than Oracle has over Java. Both Java, the language and the JVM has a specification, implemented completely independently by several companies, and the reference implementation is used as a business critical infrastructure of almost all Fortune 500 companies, many of which could single handedly continue to support and develop the platform. Meanwhile C# doesn’t have an open-source debugger..
But as I expanded a bit more here (https://news.ycombinator.com/item?id=35249701 ), Java is probably the safest bet from a code longevity perspective.
Give it one or two more years and Java is where C# is ... also aesthetically. As did JavaScript.
Java is very different and much improved from when c# branched off, but many people haven’t been following along and still think it’s the same as in 2002.
A lot of things have happened since the 90s, you know?
Textbook definition of "closed minded".
Of course nothing changed; besides the concrete wording of the PR and marketing materials.
So there is no reason to change any opinions.
Actually M$ got even more dangerous since than as they stopped to fight OSS and switched to their infamous and in this case even more concerning EEE strategy.
But sure, I know some people get blinded by their marketing efforts and don't look to close what this companies are actually doing.