edit: Sorry, it's actually:
try (var executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(...)
)
instead of `go`edit: Sorry, it's actually:
try (var executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(...)
)
instead of `go` Thread.ofVirtual().start(() -> System.out.println("Hello"))Doesn’t sound like a fair comparison thou the Java version is missing things
The Java version would require all of those in the exact same way though.
fun <T> go(body: ExecutorService.() -> T): T =
Executors
.newVirtualThreadPerTaskExecutor()
.use(body)
Now you can write: val result = go {
val result1 = submit { slowOp() }
val result2 = submit { blockingOp() }
result1.get() + result2.get()
}
or words to that effect. You can also reduce it a lot in Java too, as mentioned by another commenter. class Shortcuts {
static <R> R go(Function<ExecutorService, R> task) throws RuntimeException {
try (....) { return task.apply(service) }
}
}
and then you can write: var result = go(service -> {
var result1 = service.submit(() -> slowOp());
var result2 = service.submit(() -> blockingOp());
result1.get() + result2.get();
});
using static imports.Now if you're making a cultural point then sure, Java APIs tend to be named quite explicitly. It has advantages when searching or auto-completing, and it's easy to wrap with aliases if you want to abstract a boilerplate pattern away.
But you often want the concurrently running threads to return some results, that try-with-resources is part of the structured concurrency that helps you with closing off a branching point, similarly to how we use while loops instead of gotos. `go` in itself corresponds to `goto` basically, with many of the same negatives.