4 ms·
It's like saying the article goes out of its way not to mention Scala/Haskell's IO type. Syntactic coroutines, monadic IO, and threads are different constructs,
by pron 6y ago
It's like saying the article goes out of its way not to mention Scala/Haskell's IO type. Syntactic coroutines, monadic IO, and threads are different constructs, although they are different ways to address a similar problem -- expressing sequential (and, in contrast, parallel) composition. Virtual threads are Java threads; there's nothing "bolted". Syntactic coroutines are a kind of syntactic code-unit similar to subroutines.
Which one you prefer as a coding style is a matter of taste, but threads have some objective advantages over syntactic coroutines that go beyond syntax. For one, they don't require a split API (C# and Kotlin have two copies of their synchronization and IO APIs that do the same thing but are intended for different kinds of units, subroutines or coroutines); for another, they seamlessly integrate with the platform and its tooling, allowing use of exceptions, debuggers and profilers with little or no change to those tools. The Java platform -- the standard library, the VM, and its built-in profiling and debugging mechanisms -- has been designed around threads.
BTW, Java's strategy for targeting platforms like iOS and, later, the browser, is through AOT compilation of Java bytecode using things like Native Image (e.g. https://gluonhq.com/java-on-ios-for-real/ https://gluonhq.com/java-on-ios-for-real/). This allows you to employ the standard library as well. Kotlin's approach is different, and requires different libraries when targeting different platforms.
- jillesvangurp 6y agoIt's more than that. Java developers are switching to Kotlin; notably on Android and Spring code bases (well over 50% of backend Java projects at this point apparently). This is an article about structured concurrency and how Loom implements something that Kotlin has shipped that both Android and Spring heavily integrate with already. So, there's more than a casual relation between Kotlin co-routines and Loom. I'd go as far that they apparently took a really good look at it and somehow ended up with something that essentially implements almost 1 to 1 the same kind of things. Roman Elizarov (tech lead for co-routines, and recently the whole language) has been talking about structured concurrency a lot. I don't think he invented the notion but it definitely is what co-routines is designed to do. So, when I see an article talking about how great Loom and structured concurrency is mentioning several languages but yet somehow glossing over Kotlin, I call it out as lame. Oracle no doubt has good reasons to not want to talk about Kotlin. But to me it is clear they consider it a threat. It's not the first time that they celebrate a few features new to Java where they mention other languages as influence and yet not mention Kotlin. Recent introduction of Records is a good example. The Threading APIs date back to the late nineties and are full of complex stuff that you need to be aware of if you go near them if you care about avoiding all sorts of interesting categories of bugs, issues, and pitfalls. So much that I'd treat the occurrence of import java.lang.Thread as a giant red flag in a code review. Typically, it's a rookie mistake to use that. You shouldn't have to; there are better APIs. Kotlin, btw. treats threads just as a special case of co-routines. You can have co-routine dispatchers that represent a particular executor. This is how you separate slow blocking IO things from e.g. CPU heavy stuff, or UI event handling. So, there is no API split in Kotlin. Some co-routines use a threadpool, some don't.
- pron 6y ago> So, there's more than a casual relation between Kotlin co-routines and Loom. Not beyond the fact it was one of the things we looked at and decided to go in a completely different direction. We looked at Python, Go, JavaScript, C#, Kotlin, C++, Rust, Zig, Haskell, Pony, Scheme, Erlang, Céu, Scala and Clojure, and decided not to go down the C#/Kotlin path, which is why we ended up with a solution that is nothing alike. The positive influences were Erlang, Go, and Scheme (with a glance to Céu). That's why virtual threads share a lot of similarities with Erlang processes and Go goroutines, and borrow ideas from Scheme's (and OCaml's) multi-prompt delimited continuations, but are not at all like C#/Kotlin's syntactic coroutines. There are certainly things about Kotlin we love, like nullability types, and that we'd like to see in the Java language some day. When we do, Kotlin would have been the influence. Syntactic coroutines, on the other hand, were something to avoid. > Java developers are switching to Kotlin Just as they had to Scala in the past, many developers are switching to Kotlin, which has reached ~2-4% on the Java Platform, and might even reach 5-7% some day. That is huge, perhaps unprecedented, market penetration for a Java Platform language, but let's not get carried away. There are certainly languages we consider serious competitors, but Kotlin is still an order of magnitude in size away from being one of them. Java is so big that you can still be 20-50x smaller and still be a very popular language, yet not quite a competitor. > Recent introduction of Records is a good example. Nope. First of all, Kotlin doesn't have records. Kotlin-like data classes were something we looked at (we look at all languages) and said, we don't want that, we want records. Second, the inspiration for records was ML. So records are actually yet another example where we decided not to go in the same direction as Kotlin. This isn't to say Kotlin did something worse or better, but it did do something decidedly different. > Typically, it's a rookie mistake to use that. You shouldn't have to; there are better APIs. The same APIs Java users use today (Executors) are the ones they'll use when Loom lands. There is still no need to use the Thread API directly. > Kotlin, btw. treats threads just as a special case of co-routines. Perhaps in your desire to see Kotlin influences everywhere (and, BTW, syntactic stackless coroutines were done in C# first, at least among the well-known language) you misunderstand what Loom is. Virtual threads are threads, period. Anyway, it's perfectly fine to prefer Kotlin over Java (the language) just as it is to have the opposite preference. But I think you misjudge the influence those languages have and draw from.
- sideeffffect 6y ago> Scala/Haskell's IO type Since you've brought out Haskell's IO and Scala libraries' IOs (Monix, ZIO, Cats Effect), or F#'s Async, I think it's worthwhile to point out how they're different from the async-await approach that's in C#/Kotlin/Rust/etc. They both require "special" handling -- in C# it's the `async`/`await` syntax, in Scala it's the flatMap function or for-comprehension -- there they are similar. But their meaning is different. IO/Task in Scala doesn't represent a possibly started and under-way computation; it represents a "dead" program, yet to be started, a mere value. And it has all the advantages that mere values have, like refactoring (extract variable, ...) or restarting in case or failure and so on. Pass it into a method, return it from a method, store it in a data structure/collection, create `IO[IO[X]]`, whatever you want, just like you would with `Option[X]` or `List[X]`. In Scala, you have to differentiate between `A => B` and `A => IO[B]`, because `B` and `IO[B]` are different, but both are still values. Your program then ends up being this one big IO/Task value, which is then executed "at the end of the world". These pictures illustrate it quite well: https://twitter.com/impurepics/status/1182946618280153094 https://twitter.com/impurepics/status/1182946618280153094 https://twitter.com/impurepics/status/1180064851219144704 https://twitter.com/impurepics/status/1180064851219144704 On the other hand, async-await has none of the benefits, only downsides. You get functions of two colors, but no benefit in return. It's justifiable in Rust, because Rust aims for zero-cost abstractions. But for Kotlin/C#, it's a sad choice. The Loom approach for Java is a reasonable one. No async-await shenanigans, no funny FP/Haskell/IO business. You just use threads for concurrency as God intended them and you can have gazillions of them, because they are M:N. And I respect that, even though I'm partial to IO/Task for the reasons outlined above.
- pron 6y ago> Loom approach for Java is a reasonable one. No async-await shenanigans, no funny FP/Haskell/IO business. There's another important piece of the puzzle (putting aside any debate over the value of values): software is much more than syntax. When I think of some software construct, I don't just think how I can express and manipulate it in code, but also how I can express it and manipulate it while it is running and after it's run, i.e. present it in a profiler or assist troubleshooting when something goes wrong. In other words, how to make it a traceable, contextual entity. The "process" construct on the Java platform -- regardless of the frontend language used to write the program -- is not that of an IO type, nor is it a syntactic coroutine; it is the thread. The JVM constructs stack traces for threads; it emits profiling, monitoring and debugging events for threads; its semantics of single-stepping follow threads; its GC heuristics are or can be designed around threads and even the JIT compilers perform optimisations that are implicitly based on threads [1]. In other words, the syntactic construct is just a part of of the problem, and the goal was not just to find a good fit for the language, but also all of the other important aspects of software. Adopting any other alternative would have required introducing that new concept into all layers of the platform as well. We're building a platform, not just a language. Just to give you an example of a design issue we're struggling with now because even the mere number and duration of threads changes some of their runtime aspects: how do we perform thread dumps in a way that would tell the user what they want to know, i.e. what the different parts of their application are currently doing? Merely dumping a million stack traces probably wouldn't do the job, even if we grouped and deduped them. If we were to introduce a new kind of process, such "program dumps" would be the least of our worries; just the coordination with debugger/profilers/APM vendors would be a multi-year project. > https://twitter.com/impurepics/status/1180064851219144704 https://twitter.com/impurepics/status/1180064851219144704 OT, but, as someone who's interested in analytic philosophy and formal languages, such incorrect usages of "referential transparency" are a pet-peeve of mine. Java is more referentially transparent than Scala (at least Scala 2.x) because it doesn't have macros. It does not mean "an expression can be replaced by its value without changing [the value of the value of an enclosing expression]" but "an expression (term) can be replaced by its reference (aka denotation) without changing the reference of its enclosing expression;" hence referential transparency -- a syntactic term is a transparent representation of its reference, something that isn't true once you have macros. It's just that if your language is a pure-functional one, i.e. one with value semantics, i.e. one whose term references are values in the object domain of the language, then a reference is a value. That incorrect usage of referential transparency is nothing but a synonym for being pure functional (or of "value semantics") rather than a feature of it. > It's justifiable in Rust, because Rust aims for zero-cost abstractions. It's not that async/await is inherently more "zero-cost" than user-mode threads; they're just like that in Rust given its peculiarities. A different syntactic construct allows them to restrict recursion and virtual dispatch that would make stack size non-deterministic and interfere with their chosen memory-management strategy. [1]: The compiler inlines calls (which are on the same thread); it doesn't inline multiple monadic stages (which often entail some megamorphic callsite).