I'm impressed with how Java is shaping up. With records, pattern matching, destructuring, and virtual threads all arrived or arriving, what advantages do Kotlin and Scala bring?
It's the small things that make kotlin awesome. Not microscopic things like removing semicolons, but the left-to-right of .as?, .let and friends that allow simple things to remain simple instead of littering the code with almost-single-use names. Give those trivial intermediates a name when you think the name will be helpful, not when the syntax is unhappy without. Those aren't astronaut level language features to treat someone's Haskell-envy, but simple things that just happen to add up really well.
The stdlib of kotlin with immutable types, lots of good extension functions on for instance lists etc is hard to beat. Especially with the nice lambda syntax it makes it a joy to write functional code compared to streams and having to call separate functions by wrapping instead of chaining.
Operator overloading can be misused, but for certain things it makes stuff much prettier as well.
Scala stdlib is considerably better than the one from Kotlin, one detail I dislike about Kotlin collections is that `map` invocations always result in a List.
Kotlin's standard collection are "immutable collections" only in the sense that they are read-only. Adding an element to a Kotlin immutable set copies the whole thing,
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.
Yes, via co-routines and flows. And with some convenient extension functions you can easily deal with Java APIs that take or return streams. The structured concurrency proposal in JDK 21 takes some steps in the same direction. That's basically the higher level stuff that will make Loom more usable.
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.
Kotlin provides modern features in the Android development Java land stuck on the ancient Java 6.
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.
With Scala 3 a lot of the idiosyncrasies of scala 1/2 got fixed. (If you can get over some naming.) What is mostly brings for me is showing that composition beats inheritance and that classic OO trying to shove the real world in an inheritance tree is not the way to go.
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?
As someone who is only tangentially familiar with jvm functional languages and hasn't written java in years, "symbol soup" almost certainly has a lot of the blame.
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.
The problem I always have with OO (or imperative) languages picking up functional features is that the constraints of functional languages are, to me, among the biggest wins.
When I know that any library I invoke is literally incapable of changing the data I pass to it, that's tremendously freeing.
As a Java developer for 15 years and as a language enthusiast myself I'm curious as well. Because I definitely don't see many places where I would use pattern matching in my code or even records. Recently I wrote like 10k LoC and I used record once: in test code to return pair of values.
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.
> Because I definitely don't see many places where I would use pattern matching in my code or even records. Recently I wrote like 10k LoC and I used record once: in test code to return pair of 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.
As they say, "the determined Real Programmer can write FORTRAN programs in any language.".
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.
> Kotlin provides modern features in the Android development Java land stuck on the ancient Java 6.
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 is a common misconception. I use Java 17 on Android just fine. The only limitation is that you can't use features that rely on methods and classes introduced after Java 11, for example records. For bridging the gap between 6 and 11 Google provides a "desugaring" library.
I'm not sure how they appear in bytecode but they extend java.lang.Record under the hood, which is a new class that the desugaring library doesn't have.
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.
Until Java finally includes real nullability guarantees in its language (and its standard library) I'll stick to Kotlin when I can.
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.
Java has tons of nice additions (Lombok, NullAway, Manifold) but that's not part of the language itself. When you bind yourself to libraries like these, you're stuck waiting for them to update whenever a new Java release comes out. That can take months or years, and sometimes a library just stops getting updated at all.
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.
If you import Java libraries into Kotlin, you can still get NPEs from the libraries. And if you don't import Java libraries into Kotlin, well... you could just write Java without those Java dependencies, too.
I rather focus on the disadvantages of additional layers to debug, with their own set of libraries, and on Kotlin's case, a way to sell InteliJ licences.
Meanwhile using Java, means using JDK out of the box with no extra sugar. Pretty healthy.
Indeed, how can anyone live without a full compiler instead of an incremental one, buying additional licenses for JNI development (Clion), ten finger chords, an index system that never stops, having to manually call for specific code inspections, ...
Don't know anything about Scala. Kotlin has null safety and a bit cleaner syntax, but other than that, I don't see too much advantage over Java for backend. In Android, Java is still lagging behind a lot. Also, Jetpack Compose, a declarative UI framework is Kotlin only. Kotlin is also working on wasm (so is Java I think, but Kotlin has working examples with wasm GC) and Jetpack Compose is going multiplatform, including wasm. This video has some examples in description https://youtu.be/oIbX7nrSTPQ
I'm happy to take a crack at Go and C++, but coming from Java it is totally impossible to decipher wtf is going on in Scala. Kotlin is better, but still pretty awful.
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.
Go is great for CLIs and utilities. It compiles to native code, has a low memory footprint, and the source code is pretty readable even if you've never written Go before. And when it crashes you get a human-readable stack trace not just a core dump :)
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.
I meant it in the way that Go is not cut out for most embedded use cases due to having a fat runtime with GC.
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).
> 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.
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.
> A tiny language with only a few, but powerful, features which can be used as building blocks to express even the most advanced patterns.
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.
I can give perspective as someone who enjoys modern Java, writes Kotlin at their dayjob (and loves it), and also likes Scala 3.
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:
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)
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.
6. shrug. Many popular languages have explicitly rejected function named parameters. Use a struct/record if you want named arguments is the usual answer.
> 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)
It is possible to create custom functional interfaces in Java. One is not restricted to what is provided in the stdlib.
@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.
> 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
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)
> 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.
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.
Isn't point (5) kind of moot with virtual threads?
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.
Yeah, Kotlin does have the colored-function problem, in the same way that Node/JS has.
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.
> Isn't point (5) kind of moot with virtual threads?
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.
> I think golang shows that a synchronous, imperative paradigm wins the masses.
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.
> I never quite understand why function coloring is referred to as a problem.
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!
Yeah i think extension functions and operator overloading are biggest things in kotlin that are missing in typescript. Its very easy to add functions to existing types and chain the method calls in kotlin.
Edit: didnt know about the brand types in typescript, TIL, thanks for the example.
I'm using Linux as my solely desktop OS since over 20 years. (As it's the only usable OS)
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.
The JVM’s main value props are a world-class single dispatch runtime and GC, and a huge ecosystem of compatible OO libraries. It also doesn’t care whether you are writing Java by hand. If your team can master any of the more concise and productive languages, they should.
It’s hard for me to judge, when I’ve known Java since 1996. I might need to know Java, but I don’t need to write it. Maybe it’s enough that someone on the team knows it. Maybe it’s not really possible to master Scala/Clojure/Kotlin/Groovy/AspectJ without knowing at least some Java.
Likewise, I probably need to know C to use POSIX, but it’s so unsafe that I should not be writing it.
- 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.
I don't see much advantage from Kotlin unless you are stuck with an old jdk (like the one from Android), still, the latest jdk is still far away from current Scala.
Feature soup is kind of meaningless. The open source community has whatever it needs now.
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.
For me, the biggest pain point with Java is the lack of optional chaining <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...> and nullish coalescence <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...>. I've noticed so many libraries and JDK APIs that try to solve this problem in such a limited way. What is `Objects.toString(thing)` other than a botched `thing?.toString() ?? "null"`? And that utility method is only limited to `toString`, what if you want null-safety for other methods? You have to write that yourself. It's silly. Adding optional chaining and nullish coalescence would be a wonderful QOL change. It'd reduce code complexity, and it would flatten code (ie, hide less logic behind tiny methods).
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())`.
> 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.
Disagree. They should not be the first tool to reach for, but they are basically just safe compile-time macros (mapstruct at least is compile-time only)
This question is fundamentally flawed. People don't use Kotlin just as "a better Java" (maybe they did in 2017), Kotlin is an independent language that runs on the JVM, as well as other targets. Most Kotlin devs don't see Java and Kotlin in competition with each other or wish to use Java if it only had feature X.
> What matters is how quickly you can get code to do what you want...
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.
And yet, every single java enterprise project has a shitload of files, and compiling and running all of those takes significant time, in the 10s of seconds, all because of how Java is structured.
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.