A compact overview of JDK 21’s “frozen” feature list
vived.io
vived.io
This is how Spark's optimizer Catalyst works in Scala
https://gavinray97.github.io/blog/what-good-are-record-patte...
Kind of wild to believe this is valid modern Java:
return switch (expr) {
// x + 0 = x
case Add(Var(var name), Const(var value)) when value == 0 -> new Var(name);
These two are my favorite new JDK features by miles, along with Sealed Types. sealed interface Tree {
record Leaf(int value) implements Tree {}
record Node(Tree left, Tree right) implements Tree {}
}
var tree = new Tree.Node(new Tree.Leaf(1), new Tree.Leaf(2)); tagged-union Tree {
Leaf(int value),
Node(Tree left, Tree right)
}
I used `tagged-union` because for backward compat I think using `enum` would not be possible but really it could be anything. sealed interface Tree permits Tree.Leaf, Tree.Node {
record Leaf(int value) extends Tree {}
record Node(..) extends Tree {}
}
), it is not too terrible. Also note that rust really mixed up the nomenclature here with their enums, these were known in every language previously simply as ADTs. It makes sense for these to not have identity, while java enums are always a single instance.That is, for languages that already have features like this (e.g. Scala), will those languages be getting any benefits as a byproduct of Java getting these features?
https://medium.com/@nataliiadziubenko/java-20-pattern-matchi...
The tl;dr = no new intrinsics (for now), it uses table-switches and invokedynamic
But the compiler doesn’t treat them as anything special, it’s just a method. But the runtime is well aware of this pattern, and what the resulting, straightforward byte code looks like, and can make optimization decisions from there.
So where, perhaps, you may want try and write “clever” code to bend the compiler to your will, Java promotes “just write Java, no reason for the code to be tricky, let the compiler and VM implementations be tricky”.
But all sorts of libraries except get/set so we often end up generating them.
I didn't like how it did exceptions, that seemed a bit too much like its own little mini VM. JSR/RET etc, return addresses on the operand stack. But that's gone now.
https://docs.oracle.com/en/java/javase/20/docs/api/java.base......)
The concrete implementation in doTypeSwitch currently says “Dumbest possible strategy”:
https://github.com/openjdk/jdk/blob/master/src/java.base/sha...
There is no @IntrinsicCandidate, so I don't expect Hotspot to special-case any of this yet. The machinery described so far merely computes an array index, and there is a subsequent lookupswitch opcode that selects the appropriate case body to run.
I'd be somewhat surprised if Hotspot could unroll the checking loop and eliminate the subsequent switch. So the Java implementation looks rather bad from a performance point of view (but it's obviously designed for compact bytecode and future optimization). Scala could certainly do the same (the magic of invokedynamic is that it's not magic), and probably much better in many cases, even on current Hotspot.
This is one reason why invokedynamic is so interesting: it is possible to switch implementations without recompiling everything.
(I say this as a Java dev who occasionally gets tempted to try to help out in repos that are Kotlin/Scala, and gives up very quickly.)
Also, devs who use Go and C++ usually have a good reason (embedded systems and such), but Kotlin and Scala use seems to be motivated mostly by vaguely hipster-y annoyance with Java 8. And, like, sure, if you are annoyed by Java 8 and then use a language designed to have nothing in common with Java 8 except that it can import Java libraries and run in the JVM... well, it's gonna be a pain in the butt for your colleagues who do Java all day. And some of them aren't going to make the effort to work with it.
I hate to admit it as someone who enjoy trying new languages and enjoyed my time using haskell a long time ago, but scala 2.x was definitely the most confusing and intuitive language i have ever used professionally. Really felt like a collage of desperate features without any coherence between them.
unintuitive, perhaps?
A tiny language with only a few, but powerful, features which can be used as building blocks to express even the most advanced patterns.
The problem are just the people "holding it wrong".
If you try to use it as Java++ Scala will be more cumbersome than Kotlin.
If you try to use it as Haskell this will become very painful quite quickly.
You need to use it as Scala… This means you need to embrace its features and philosophy. Just use the best parts of OOP and FP together!
And yet from the scala 3 website itself :
> One underlying core concept of Scala was (and still is to some degree) to provide users with a small set of powerful features that can be combined to great (and sometimes even unforeseen) expressivity. For example, the feature of implicits has been used to model contextual abstraction, to express type-level computation, model type-classes, perform implicit coercions, encode extension methods, and many more. Learning from these use cases, Scala 3 takes a slightly different approach and focuses on intent rather than mechanism. Instead of offering one very powerful feature, Scala 3 offers multiple tailored language features, allowing programmers to directly express their intent:
> The problem are just the people "holding it wrong".
If enough people have the same problem with a programming language, at one point it become the problem of the language, not the people.
> Just use the best parts of OOP and FP together!
Assuming that one can cleanly extract the best part of OOP and FP without bringing the baggage of eiter. It's not clear to me that those part don't have a certain level of "contradiction" which leads to the whole beeing less coherent than just OOP or FP.
> Kotlin and Scala use seems to be motivated mostly by vaguely hipster-y annoyance with Java 8.
Now that I think about it they seem like Instagram of language/code/devs. Mostly obsessed on surface level, superficial syntax features. IMO it is great in same sense as cooking with meal kit cooking is superior to cooking with grocery shopping.
Just.. drop go from that. Go is closer to JS than to C++/embedded.
But I've never seen a large-ish Go application that I didn't hate. Because, yeah, as you said, it's closer to JS in a lot of ways.
It is ok for CLIs and small utilities, but I can’t really stand looking at it (they went with a type syntax that is neither C-like, neither Haskell-like and is absolutely unreadable to me, even though I can usually get the gist of any language from having seen my fair share of syntaxes).
Scala was an academic experiment on how to match nicely object oriented world with functional programing paradigm that got some hype because Java development was crawling like a snail. I am not sure if this experiment was successful after all, though.
Also, it got Java to move up development, together with the move to cloudnative.
From a language enthousiast point of view I’m still curious why Kotlin and now these features in Java get so much traction while most have been available for 20 years in Scala. Was it marketing? Was it people? Was it symbol soup? Who knows?
May be I'll replace some `if`-s with pattern matching, just like I use `instanceof` pattern matching today, but that's absolutely minor thing which is hardly worth mentioning.
I would even dare to say that lambdas and `var`-s for me are questionable features.
Actually as I grow older, I appreciate ascetic nature of Go and sometimes I think that using Java 1.4 might actually be preferable for many codebases.
I understand that for code which manipulates trees like compilers with their ASTs, pattern matching might be god send. The thing is... 99.99% of developers don't write this code, so optimizing language for it is strange goal.
Sometimes I think that some language features are driven by hype, even with Java.
I always happy about runtime improvements, though. And Java delivers in that front, so I can tolerate pattern matching and streams if I can get virtual threads and struct values.
Without knowing your application domain or seeing the code, it's hard to guess why. But one reason is that pattern matching and union/algebraic type tend to go hands to hands. So depending on how your data domain was modeled, it is normal that you don't find yourself not needing to pattern match as much.
In general,it's normal to find one self "not needing" a feature that one is not used to, professional are very good at structuring things is a way that work best with the current toolings.
> I understand that for code which manipulates trees like compilers with their ASTs, pattern matching might be god send
Very true, but i can assure you , pattern matching (combined with record and ADT) are very useful tools in general computation as well.
ADTs and pattern matching go hand in hand, and they - along with immutable structures - are the cornerstones of functional programming. They provide you very strong compile-time guarantees in a very elegant and readable form. I like to write code this way, and out of 10k LoC I would estimate at least half is ADTs and pattern matching for me. Maybe more.
Too bad that although Java's ADTs are coming along nicely, pattern matching is in it's infancy (due to limitations inherent in Java). This is the single language feature I miss the most from Java and Kotlin from Scala.
Java is simple. There were few operators, and really only one way to call a function. All function calls require parentheses. Conventions are available for pretty much any problem you can think of.
In functional land, you don't need to bother with calling a function a factory, or adapter, or whatever overwrought GoF pattern was maladapted. Despite being verbose and convoluted, Java was all basically the same things- classes and methods and a few annotations, and easy to Google concepts.
Enter symbol soup. Now, your conventions aren't named, you have to recognize them from experience. That creates writer's anxiety- even if I understand what I am reading, if I need to start from scratch I don't know that I'm organizing things right. There are multiple symbols that apply, combine or call functions, googling symbols is hard, asking questions is hard in meat space without a screen to show the unfamiliar syntax, and that's all table stakes before you get into understanding performance implications, code organization, maintainability, etc.
If I, hypothetical developer, don't know these things and don't have someone to hold my hand, but I DO know java, what's the point of learning the functional language?
This is the same problem F# had. Over time, C# kept getting all the good bits from F# without the baggage. Java has taken the longer road to get there, but today, if I were to pick one, the value proposition of the functional language appears to be on a trajectory of disappearing.
When I know that any library I invoke is literally incapable of changing the data I pass to it, that's tremendously freeing.
These days you can use Java 8 features with no issues, and Kotlin isn't really solving the fundamental problem of runtime version on target devices — it's just spitting out Java 8 bytecode by default. Android build system already _desugars_ newer Java language features for older runtimes, and they could do the same thing for all the new fancy features. I guess they choose not to because Kotlin is already there.
That said, I do believe Kotlin is still more concise, has better nullability handling, and upcoming exciting features of its own like compiler plugins
It turns out that Kotlin being able to consume Java libraries is worthless if Android can only use jurassic libraries.
Switch expressions and all the pattern-matching stuff though — that's all implemented entirely in the compiler. I use it on Android quite extensively with no issues.
Here are the things that if Java had, I probably wouldn't see a reason for other languages:
1. Lack of first-class lambda syntax. In Kotlin/Scala you can write something like:
fun doSomething(handler: (String, Int) -> Foo): Blah
In Java, all you have are the "Function<>" and related interfaces, which are clunky to use.2. Opaque types (Scala 3). These have been one of the most impactful programming features I've ever used, and I sorely miss them in languages that lack them.
object Foo:
opaque type UserId = Int
opaque type Email = String
def mkEmail(s: String): Email = s
def findUserIdByEmail(email: Email): UserId = 42
import Foo.*
val email: Email = mkEmail("foo")
val valid: UserId = findUserIdByEmail(email)
val invalid1: UserId = findUserIdByEmail("bar") // error: can't use String as Email
val invalid2: UserId = 123 // error: can't use Int as UserId
3. Union types (Scala 3). You can emulate them in Java/Kotlin with Sealed Types but it's much more verbose. type JsonScalar = String | Int | Boolean
type Json = JsonScalar | Map[String, JsonScalar] | List[JsonScalar]
// Makes writing functions that take multiple argument types much easier:
def handle(it: String | Int | Boolean): Unit = ...
4. Context-oriented programming with "given/using" in Scala 3 and "context-receivers" in Kotlin.This one is harder to explain succinctly but essentially it allows you to decorate methods/classes with required "contextual" args.
Instead of passing them as regular function arguments, you must invoke the function inside of an "environment"/"context" where the requirements are satisfied.
This makes threading dependencies through your code much easier, and eliminates the need for dependency injection frameworks in many cases.
interface Logger {
fun log(message: String)
}
object ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
ctx(Logger)
fun doSomething(): Int {
log("Hello")
return 42
}
fun main() {
val logger = ConsoleLogger
with (logger) {
doSomething()
}
}
5. First-class support for asynchronous programming. With "suspend" in Kotlin and a current prototype being done in Scala 3:- REPO: https://github.com/lampepfl/async | SLIDES: https://github.com/lampepfl/async/blob/main/scalar-slides.pd... | YOUTUBE TALK: https://www.youtube.com/watch?v=0Fm0y4K4YO8
6. Passing arguments by name, rather than positionally.
fun doSomething(a: Int, b: Int, c: Int): Int = ...
doSomething(a = 1, b = 2, c = 3)
7. Tuples (Scala). They're like anonymous data classes/records. val t: (Int, String) = (1, "foo")
val (a, b) = tKotlin has a few things that TS doesn't have but overall, I'd rate it very similarly. I probably enjoy writing TS a bit more than Kotlin.
EDIT: TS also has Opaque Types, sort of. They call them "brand types" or other names, it looks like this:
declare const opaque: unique symbol
declare type Opaque<T, K extends string> = T & { readonly [opaque]: K }
type Email = Opaque<string, 'Email'>
function mkEmail(email: string): Email { return email as Email }
function takesEmail(email: Email) { console.log(email) }
const email = mkEmail('foo@bar.com');
takesEmail(email);
takesEmail('wrong'); // errorEdit: didnt know about the brand types in typescript, TIL, thanks for the example.
parameter -> expression
(parameter1, parameter2) -> expression
(parameter1, parameter2) -> { code block }
and function signatures like the below might be "clunky" from your point of view, but IMHO are more clear since they document explicit types. (and you can navigate to their javadoc) Blah doSomething(BiFunction<String, Integer, Foo> fn)
2. Conceded. There are some JEP's around this but they all got rejected.3. Already covered in existing discussion. Verbosity level is fine.
4. Context oriented programming can easily be achieved using AOP in java. But frankly is readability and maintainability nightmare in any large project. I have seen projects/apps/libs that used this paradigm (in several languages) be re-written to explicitly designate all contexts.
5. Java with virtual threads now has far better support for async programming than Kotlin or Scala. It is now competitive with Golang in async ease-of-use. https://blog.rockthejvm.com/ultimate-guide-to-java-virtual-t...
6. shrug. Many popular languages have explicitly rejected function named parameters. Use a struct/record if you want named arguments is the usual answer.
Blah doSomething(BiFunction<String, Integer, Foo> fn)
What happens when you want to pass a function of arity 3, 4, 5 or 6?Also, it's nice to be able to give identifiers to the arguments:
// In Kotlin (absurd example, just to prove a point)
fun withHandler(
handler: (name: String, age: Int, isAdult: Boolean, callback: (Int, Int, Int) -> Unit) -> Unit
) {
handler("John", 25, true) { println(it, it, it) }
}
// In Java:
interface Handler {
void handle(String name, int age, boolean isAdult, Callback callback);
}
interface Callback {
void call(int a, int b, int c);
}
class JavaClass {
public static void withHandler(Handler handler) {
handler.handle("John", 25, true, (a, b, c) -> {
System.out.println("a: " + a + ", b: " + b + ", c: " + c);
});
}
} @FunctionalInterface
public interface VargsFunction<T,R> {
R apply(T... t);
}
@FunctionalInterface
public interface QuadFunction<T, U, V, W, R> {
public R apply(T t, U u, V v, W w);
}
Of-course Java is nowhere as flexible as C++ in this regard which has variadic templates, template parameter packs and template-template parameters. Well, you can do this with currying if one is feeling lazy, but obviously not recommended: Function<One, Function<Two, Function<Three, Function<Four, Function<Five, Six>>>>> func = a -> b -> c -> d -> e -> 'z';
The second point (named parameters) is one that several language designer greybeards (not just Java) have taken a deliberate design decision against for varying reasons. Use builders/records/structs is the usual advice given here anytime you ask them this question.green thread/stackfull coroutine such as in go/java and stackfless coroutine as in kotlin are well know solutions for introducing async programming in a way that feel natural to dev. They both have strength and limitation, i don't think one offer "far better support for async programming", and even more importantly they are not mutually exclusive and can be used together depending on one needs (see https://www.youtube.com/watch?v=zluKcazgkV4)
Only if your problem set matches one of using threads. There are other async problems that don't fit cleanly into a "force it to be a blocking thread instead" model. In particular those in front ends where being on a specific thread at specific times is important.
Green threads work great for server-style async, which is where go is seeing success. Then again servers are probably the last major usage of OpenJDK, so copying Go's tradeoffs here probably makes sense for it. But it's not unambiguously "the best way to do async"
That signature is awful. I never remember if the return type is first or the last in the list. Also the "generic" types don't document anything, by design. Yes it's an actual type but it's too generic to have any value over something like Kotlin or Scala declaration. It's impossible to tell from this signature what the input and output types are supposed to represent.
Almost always it's better to define a new functional interface than using BiFunction or similar.
I think golang shows that a synchronous, imperative paradigm wins the masses.
I'm not too brushed up with Kotlin suspend, but does it suffer from the classic "function coloring" problem that plagues other solutions? I've dabbled in functional effect systems in Scala, for example. I really enjoy them, but my coworkers sure don't when they realize that to perform some IO in a new place they will have to update a huge stack of type signatures to be wrapped in an IO monad. Async/await in javascript has the same issue, though the syntax is a bit friendlier.
My great hope for virtual threads in Java is that we can bring great IO performance and scalability, on par with golang, without retraining devs.
Loom and Virtual Threads are one area I'm not as familiar with.
What I do know is that you can configure virtual threads as the coroutine dispatcher for Kotlin coroutines, and in a recent video Roman Elizarov talked about how the default "Dispatchers.IO" could theoretically leverage VT's in some future JDK.
I am not sure go shows that... Attributed the "poplarity"of go solely to its async model is kind of a leap.
> I'm not too brushed up with Kotlin suspend, but does it suffer from the classic "function coloring" problem
I never quite understand why function coloring is referred to as a problem. Including the async/non-async nature of a function in its signature is as natural as using any other monadic types as a return, such as Optional for example.
> My great hope for virtual threads in Java is that we can bring great IO performance and scalability, on par with golang, without retraining devs.
I think that's sometime a conflation that happens : virtual thread usually ease scalability at the the cost of performance.
For exactly the reason I said: colored functions are poison. Changing one type is easy. Changing a whole stack of types is tedious. And that's before you realize you tests have stopped compiling as well!
It might be but we don't know yet. I have yet to see a large scale application written with virtual threads (for the good reason that it is barely out of development!) I'm reserving judgement until I see virtual threads in use outside of toy examples.
Operator overloading can be misused, but for certain things it makes stuff much prettier as well.
In general the theme of that release seems to cherry pick a lot of stuff that Kotlin and Scala have been supporting for some time. That's a good thing. Java developers have been missing out on a lot of good stuff. There's quite a bit more of that of course where this came from.
https://github.com/JetBrains/kotlin/blob/924c28507067cbfbf78...
This is not that different from Java unmodifiable. In contrast the Scala immutable Set supports constant time addition,
https://docs.scala-lang.org/overviews/collections-2.13/perfo...
This isn't just an ivory tower concern either. Because Kotlin immutables aren't actually immutable (they just don't expose mutation interfaces) this means that if I get a ref to an immutable Set, I can't modify it but I can't rely on it not changing because it's still back by a mutable one.
That it's not Java. It's a huge thing with some developers not wanting to touch Java for <insert reason> but would be fine with Kotlin or Scala.
It is like using UNIX and not wanting to understand, touch, C.
If people want to ignoring the foundations of their toolbox, that says a lot about the skillset.
Likewise, I probably need to know C to use POSIX, but it’s so unsafe that I should not be writing it.
- understanding stack traces
- writing idiomatic bindings for libraries
- debugging why Java frameworks fail consuming jar files whose bytecode was generated in a different way
- debugging why a jar misbehaves after a JVM bytecode rewriting tool messed up the bytecode sequence used to emulate other language semantics on top of the JVM
- adopting libraries whose compiler plugins and annotation procesors only understand Java semantics
- mapping debuging information and telemetry data from tooling like JFR into the original code of the guest language.
At the same time I hate C and everything around it!
Both things go quite well hand in hand. You don't have to touch any C-madness most of the time. Even when you compile stuff yourself, like for example the Kernel, you almost never have to interact with C directly.
I would still prefer an OS written in Scala, but we don't have that at the moment. So I guess I will stick with Linux for the time being.
Many "alot" of java programmers in the US cant code to JDK8 already. I love the JVM but these Java releases are not being adopted on any real scale for a reason. They break things. And syntax sugar is boring and unnecessary for a crew of software engineers that have no real ethos surrounding records or any of these features.
They are just being rolled out to appease devs from other ecosystems. They will not form a new or better method for building systems or improve performance.
I do a lot of serialisation. You have no idea how much I'd love to be able to just to `data.set("thing", this.valueEnum?.name())` instead of having to do `data.set("thing", this.valueEnum == null ? null : this.valueEnum.name())`.
You may be already familiar with this, but mapstruct is a godsend package that easily beats any kind of optional chaining.
Elvis operator. Drops mike.
Meanwhile using Java, means using JDK out of the box with no extra sugar. Pretty healthy.
This doesn't change much for me, because I can't imagine writing Java without IntelliJ. Eclipse is just a disaster by comparison.
These improvements are still nice for when you're stuck dealing with Java code, but in my experience getting projects to run on the latest version of Java isn't very easy with various dependencies all needing support first.
If Java were to include NulLAway in their standard language, which they clearly can do if they wanted to, I would consider it to have feature parity with other modern languages.
https://mail.openjdk.org/pipermail/valhalla-spec-observers/2...
But even if they do, I'm guessing that's still going to be a long ways away.
- higher kinded types
- null in types
- for comprehensions
- macros
- opaque types
- implicits/type classes
- persistent immutable collections
- EDIT named & default params
Yet literally none of those things actually matter for any sort of real world application.
What matters is how quickly you can get code to do what you want, and Java/Kotlin lost that plot almost a decade ago.
it's pretty quick to start with java (and presumably kotlin too - not too familiar with it).
I argue that it's even faster than with javascript. You at least don't have to toil with the build tool (java == maven).
This is the falsest statement I have read in a long time.
Maybe, if you write once and then throw it away (like a use-once shell script).
Otherwise you will have to maintain your code, and then readability and safety trump everything else. If not, it is about performance, but then a lot of time is spent on fine-tuning.
Trying to edit something means you have to dig through layers of inheritance into factories and injection frameworks, debugger is a slow piece of shit that almost needs ide wrapper to run, and as far as safety goes, people seem to have forgotten about log4shell, REALLY quick.
You can believe that strong OOP and all the theoretical bullshit in JDK21 matters, but in the future, Java developers will be the first to go as the world moves towards much smaller dev teams that are well versed in a super high level, AI powered language+compiler, where you can knock out production web apps in a day.
Or maybe I'm missing something in which case would you point me to a JEP or an article about destructuring in Java?
EDIT: nevermind, I should have checked JEP 440, which is about destructuring record patterns...
Aside from that, immutable / readonly collections and null safety are two big reasons for Kotlin or Scala.
You can find mirror on Substack: https://vived.substack.com/p/the-compact-overview-of-jdk-21s...
EDIT: We did the redirection to the Substack mirror.
This is equivalent to forced purchase required to use the restroom at a venue. Don’t be like that. Compassion is good for business.
However, you don't need to subscribe - you can close the popup and carry on!
Good write up though and appreciate the KotH meme.
There is, you click outside the modal. Or hit Escape key.
Being compatible with Java was one of the goals for Kotlin. We soon have a lot of features in the JVM mother language that are solved different in Kotlin. For example:
- Data Classes vs Java's Records
- Nullability
- String Templates
Not diverging from the core language is what made TypeScript successful on a long term. This won't work for Kotlin (and was not a goal). It will be interesting to see whether the languages will diverge even more - maybe to an extend where they will become incompatible - or the interop will converge somehow. Diverging languages will certainly make the interop harder.I think it's a very expressive and elegant language if you use its syntax properly.
Maybe some of kotlin's "workarounds" will be converted into the JVM equivalents in the backend.
Kotlin cannot choose the same route as TypeScript, since Java is already statically typed (even if that typing is not always very strong). There is no point in adding type annotations to it. Most of the improvement that Kotlin originally sought (and still seeks) to have on Java are in syntax and semantics. This means Kotlin has to modify the syntax and cannot just be a simple transpiler that naturally incorporates new Java language features.
The interop strategy that Kotlin has chosen instead is: 1. Keep track on what Java is doing. 2. Add compatibility to Java language features when they are released. 3. Avoid introducing incompatible features too quickly when Java is developing the same thing. 4. If the Java development direction is settled but the feature is not released yet, introduce features that have a clear upgrade path for compilation and interop on new Java releases.
Kotlin is already compatible with Java records (just add the @JvmRecord annotation to a data class). This annotation forces the Kotlin data class to be immutable and the compiler will generate a java record for you if you target the JVM.
Nullability marking is not something that is currently being worked on for Java. Some publications have misleadingly modified the title for "JEP 401: Flattened Heap Layouts for Value Objects (Preview)" [1] to "JEP 401: Null-restricted types", because this very early proposal mentions possible interactions with null-restricted types - but the null-restricted proposal doesn't even exist yet. It's quite hard to predict how Kotlin would be compatible with a feature that may or may not come around 2030.
String templates are still a preview feature in Java 21, so don't expect to see them in production before Java 25 in September 2025. String Templates, like string interpolation, is essentially syntactic sugar, but it provide a better story for type-safety and flexibility than classic string interpolation. Kotlin already solves the simple interpolation cases with its string interpolation, but I agree it leaves some things to be desired (multi-line handling is especially painful[2]). The type-safety/flexibility story is generally handled in Kotlin with type-safe builders[3], and it's often a better solution in my opinion, but there many cases where a string template would be more readable.
The main reason Kotlin might need to support string templates, is that all this syntactic sugar has an interface: the StringTemplate interface. Java libraries may rely on it on the far future[3], and then Kotlin will need to maintain compatibility somehow. JetBrains are not ignoring this issue of course. You will find multiple tickets on their tracker talking about custom string interpolation, that even predate the String Templates JEP[5], but it's currently a wait-and-see-approach.
I think the most important set of features for Kotlin to look at is Project Valhalla[6]. It's an even bigger revolution than project Loom. Kotlin is already preparing for this, with value classes[7]. I believe the main reason that value class usage in Kotlin is so highly restricted in Kotlin right now, is that they are waiting for Project Valhalla, and do not want to give up interop later.
You can also think of other past examples when Kotlin did introduced a future earlier than Java, and then gradually made it interoperable. The best example is closures. Kotlin supported closures since its early beta versions (before Java 8 was released), and for a long time supported generating Java 6 bytecode. Since Java 8 closures relied on a new JVM bytecode instruction (InvokeDynamic), Kotlin used a different mechanism to implement closures. When Java 8 was released, they could easily maintain API compatibility with Java, since they are always invoked through an interface. Kotlin eventually added support for generating lambdas in the same style as Java, but they only made this default in Kotlin 1.9[8], to maintain maximum compatibility. This didn't affect interop (which was already resolved since Java 8 was released), it only had impact on bytecode size and perhaps a small performance impact in some cases.
I think this list show that Kotlin is currently handling future interop quite well.
[1] https://openjdk.org/jeps/401
[2] https://youtrack.jetbrains.com/issue/KT-46365/Multiline-stri...
[3] https://kotlinlang.org/docs/type-safe-builders.html
[4] Java is a conservative language and most Java deployments are many versions behind the latest LTS. Until last year (https://newrelic.com/resources/report/2022-state-of-java-eco...), most of the Java systems in production were running Java 8, and only recently Java 11 started becoming the dominant version, with Java 8 strongly trailing behind. Java 21 is coming out this year, but Java 17 is barely deployed anywhere. For this reason, you'll find most libraries target still target Java 8 or Java 11, and avoid Java 17 features like record. Since String Templates will be out only in 2025, I don't expect to see libraries requiring them in production before 2030.
[5] https://youtrack.jetbrains.com/issue/KT-16366
[6] https://openjdk.org/projects/valhalla/
[7] https://youtrack.jetbrains.com/issue/KT-42434/Release-inline...
Assuming:
* 1 page/visit, which is the modal value based on my analytics
* the period where it is on the front page is 6 hours (I don't know this, but I could if I could be bothered to go back and look; I don't have granularity down to the minute, though)
That is 20000 requests/(66060), which rounds to 1 request/second.
Of course, there are all the other assets (JS, images, etc) that you need to account for as well. And it might spike to higher.
I think it’s more likely that there’s a big flood at first and then it drops off towards a low number.
Why not? It gets worse and worse as we aim for more abstractions and layers of tooling.
Where the latest hype is server rendering React in NodeJs for what instead code by a static site...
And that’s exactly what they are going for, AFAIK.
You as a programmer should first and foremost care about the semantics that you want to express in your programs. In many cases it means that an object you use has and need an identity. But you may find so that it doesn’t make sense in a given case, like a date, or a coordinate — so you can express that it doesn’t have identity, allowing for more freedom on the compilers part.
Finally, you may even say that an implicitly zeroed object makes sense as a default for your class, and when this particular case happens and your object can’t be null, the compiler can even completely inline/flatten your data. But the performance improvements are generally not the goal themselves, they are neat advantages you may get by restricting your semantics.
I think what is semantics vs implementation details depends on the task at hands. From my understanding, the need for value types mainly originate from the need for precise control of memory layout, In which case the representation is part of the important semantic. C#/.Net seems to have a simpler approach by just providing representation flatness as a tool and let the developer chose what they want to do/focus on.
> In many cases it means that an object you use has and need an identity. But you may find so that it doesn’t make sense in a given case, like a date, or a coordinate — so you can express that it doesn’t have identity, allowing for more freedom on the compilers part.
If this is confusing it to me, the need to identity seems to be a property of a specific object instance, not really a property of a class/type.
> But the performance improvements are generally not the goal themselves
I am not sure how true this is, the design document itself reference numeral code performance as one of motivating factor.
> when this particular case happens and your object can’t be null, the compiler can even completely inline/flatten your data
As someone who have "worked" on quite of bit of runtime/compilers, "compiler can" always turns out to be "compiler doesn't". This idea of a compiler to auto-magically deciding and optimizing data layout sound great in theory, but in practice is very very hard to achieve in most meaningful ways.
Of course performance is a goal, but they asked the fundamental questions around the topic, and managed to boil it down to something that will also solve another painpoint of the language at the same time, and help heal the rift between primitives and objects.
And your last paragraph is true in general, but the explicit goal of all this is to restrict the possible semantics of code to enable optimizations - so in the end a compiler-enforced nullable, primitive class will be reliably flattened.
I don't think it fair to say that the addition of struct in C# make it a more complex language. If anything ( well i might be bias here, since i am mainly a C++/native dev.) it make it explicit the difference between reference semantic and value semantic in a way that is very easily identifiable.
> solve another pain point of the language at the same time, and help heal the rift between primitives and objects.
From my perspective , this is root cause of the issue. A "primitive value" and an object (in the java/small talk tradition) are not the same category of thing and do not belong on the same hierarchy. It seems to me that somewhere in mid early to mid 2000, java/jvm looked at C++ and wanted to have unified type hierarchy or somehow treat values and object as the same thing. This is only possible in C++ and other native languages because those do not have "object" as defined in small talk.
The main issue with the primitive and object being different were that one can't defined additional primitive types with more complex behavior and that the generic system doesn't work well with primitive types. And now instead of addressing those two simple problems effectively, we have project valhala which took more than a decade to arrive to solution that looks to me less elegant.
Sometime two simple solution are better than one big abstract design.
> And now instead of addressing those two simple problems effectively
Those are not all simple problems.
It's part of Valhalla, as a pre-requirement for heap-flattening.
Ctrl+F for "Null-restricted type". The strawman proposal syntax is "Foo!".
When inlined, a null-restricted class type should have a heap storage footprint and execution time (when fully optimized) comparable to the primitive types. For example, a Point!, given the class declaration above, can be expected to directly occupy 128 bits in fields and array components, and to avoid any allocation in stack computations. A field access simply references the first or second 64 bits. There are no additional pointers.
For a summary of current status and what it means for you as a developer, I recommend the linked email here and the author's comments in the thread:https://www.reddit.com/r/java/comments/13xtog3/valhallas_lat...
https://mail.openjdk.org/pipermail/valhalla-spec-experts/202...
Great read.
TLDR: It is hard to decide what the defaults should looks like, so they plan to add special markers (! and ?) to allow programmers to pass that detail to objects with "undefined nullability" (without ! or ?).
Useful lesson, but until you have massive traffic happen once, it's overengineering :) .
My blog uses a custom CDN I wrote in Assembly for maximum performance.
I also wrote a custom browser (Guess what language) so my visitors browsing habits weren't shared with Google via Chrome.
I will go to the ends of the Earth to protect my users' browsing habits.
From my perspective, you should do what is best for yourself and your users. I think you should definitely be leveraging things like CDNs that are low effort and high impact to save yourself time and help your users.
Not using things that are closed source and from megacorps feels like a luxury that a lot of people cannot afford. Not monetarily, but in terms of time and effort. When I'm working on a side project I want to remove all the friction I possibly can so I stay motivated and keep working on the project.
But if you have decided to self host, I would suggest reaching for one of the many FOSS caching reverse proxy solutions first.
We experienced an astounding increase in traffic, nearly 100 times our usual volume
You can find mirror on Substack: https://vived.substack.com/p/the-compact-overview-of-jdk-21s...
By the way, .NET already has structured concurrency via Dataflow.
Personally Asyc/Await is the only thing keeping me from the C# ecosystem.
If I am not mistaken it was on this session,
"ASP.NET Core and Blazor futures, Q&A"
https://build.microsoft.com/en-US/sessions/4cfe374e-a9a0-4a8...
If I am not mistaken it was on this session,
"ASP.NET Core and Blazor futures, Q&A"
https://build.microsoft.com/en-US/sessions/4cfe374e-a9a0-4a8...
Preview features require a special flag when compiling and running them, and they won't run on newer versions of the JVM. I don't expect to see StructuredTaskScope in common production use before the next LTS version is out.
But it doesn't mean you cannot have structured concurrency before that. Even in language that mostly enforce Structured Concurrency like Kotlin, it's still a library feature. Even the original blog post which formulated this concept, described a library that implemented structured concurrency for Python[1]. You can pretty easily implement structured concurrency yourself by creating your own implementation of StructuredTaskScope, if you need it right now. You can even structured concurrency in C#[2] or Go[3].
[1] https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
I remember when 1.5 came out and Sun's marketing folks insisted it be called Java 5. I think thats when they jumped the shark, and haven't corrected course back since, versioning-wise
And I wonder if someone keeps track of what the proper traditional Java major version should be now. I'd guess its on 2.x or 3.x at best
What does that even mean?
- Scoped Values: https://openjdk.org/jeps/429
- Panama FFM 2nd: https://openjdk.org/jeps/434
The changes in the Panama API were so drastic between 19 and 20 that any code you had written would no longer work. Which is the point of interim releases like these.
I happen to have a lot of experimental Panama code that has been continually evolving with these non-LTS releases since the initial release.
Just wanted to point out there wasn't _really_ nothing new, even if it might have been somewhat pedantic.
In practice, you could say that Java had only one major version bump: from 8 to 9, when it closed down internal APIs with Jigsaw and gave away Java EE to become Jakarta.
The only seeming standard is that big integers are bad. ;) But don’t tell Chrome that.
What's your take on Chrome and Firefox?