<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hello Holo,<br>
<br>
Thank you for putting this together — this is exactly the kind of
concrete exploration I was hoping might emerge from the
discussion. A working PoC that actually exercises
StructuredTaskScope and Joiner semantics is far more valuable than
abstract debate, and I appreciate the care you took in documenting
the caveats.<br>
<br>
Taken on its own terms, this convincingly demonstrates an
important point: “exceptions as values” can be expressed today,
within the current JVM and Loom architecture, without special
runtime support. Your implementation makes that clear, and it’s
helpful to see how little machinery is actually required to model
“all succeed or capture first failure” explicitly.<br>
<br>
What I find most interesting here isn’t whether this exact Result
type should exist, or whether this particular Joiner is the
“right” abstraction — it’s what the PoC reveals about the design
space.<br>
<br>
A few reflections, more observational than critical:<br>
</p>
<ul>
<li>Your implementation makes failure aggregation explicit rather
than ambient. The fact that this logic lives in the Joiner —
instead of being implicit in exception propagation — is
precisely what makes the behavior legible. That’s a real
conceptual difference, regardless of which model one prefers.</li>
<li>At the same time, the caveats you list are telling. The need
to still catch InterruptedException, the inability to prevent
timeout configuration through ConfigFunc, and the fact that
timeout semantics live outside the Joiner unless reimplemented —
all point to the same underlying reality: failure, cancellation,
and time are still split across multiple semantic channels.</li>
<li>None of this is a knock on Loom or STS. If anything, it
reinforces how much structured concurrency already clarifies
lifetimes and ownership — and how the remaining friction shows
up primarily around how failure and cancellation are represented
and composed.</li>
</ul>
<p>I also want to emphasize: I’m not reading this as “here is the
alternative Java should adopt.” Rather, I see it as evidence that
there are still open questions worth examining:<br>
</p>
<ul>
<li>Which aspects of failure benefit most from being explicit and
value-oriented?</li>
<li>Which aspects genuinely benefit from stack-based propagation
and VM-native support?</li>
<li>And where does cancellation sit — error, control signal, or
something orthogonal — especially under parallel composition?</li>
</ul>
<p>Your PoC doesn’t answer those questions definitively, but it
sharpens them considerably. That’s exactly the kind of
contribution that makes a technical discussion productive rather
than ideological.<br>
<br>
Thanks again for taking the time to build and share this — it’s a
strong piece of evidence that the space is still worth exploring.<br>
<br>
Cheers,<br>
Eric</p>
<div class="moz-cite-prefix">On 2025-12-19 1:39 AM, Holo The Sage
Wolf wrote:<br>
</div>
<blockquote type="cite"
cite="mid:CAKswmE6zqA3=gY5Ud-Ufrd4Wd2enBu=prE6VPjZcaU_6O3WQcw@mail.gmail.com">
<div>Hello Eric,</div>
<div><br>
</div>
<div>Here is a simple implementation of "exception as values"
structured concurrency *using the current architecture* as a
Proof of Concept, this is a working example in Java 25 with the
flag --enable-preview.</div>
<div><br>
</div>
<div>Couple of notes: </div>
<div>1. I used the most on the nose naive implementation of
Result<Value, Error> type just as a Proof of Concept, but
this can be replaced with a more sophisticated type.</div>
<div>2. I didn't implement the timeout feature as "exception as
value", meaning using this PoC the only way to use timeout is
via the ConfigFunc which *will* throw an exception on timeout. </div>
<div> - It is possible and relatively simply to implement
timeout inside the Joiner and use that instead of the ConfigFunc
feature of timeout and thus bypass this point</div>
<div>3. This is specifically a "on all success return stream of
results" implementation, this implementation can easily adapted
to all other usecases via minimal changes</div>
<div><br>
</div>
<div>Minus the above caveats, this implementation is production
ready. </div>
<div>The only actual downsides I can think of about this
implementation are:</div>
<div>1. Even though InterruptedException should never be thrown,
you still have to catch it</div>
<div>2. There is no way to prevent users from setting timeout via
ConfFunc even if `AllSuccessfulResult` were to implement timeout
mechanism</div>
<div><br>
</div>
<div>
<div
style="background-color:rgb(30,31,34);color:rgb(188,190,196)">
<pre
style="font-family:"JetBrains Mono",monospace;font-size:9.8pt"><span
style="">@SuppressWarnings</span>(<span style="">"preview"</span>)
<span style="">void </span><span style="">main</span>() {
<span style="">try </span>(<span style="">var </span>scope = StructuredTaskScope.<span
style="font-style:italic">open</span>(<span style="">new </span>AllSuccessfulResult<String>())) {
scope.fork(() -> <span style="">"Holo"</span>);
scope.fork(() -> <span style="">"Test"</span>);
<span style="">var </span>x = scope.join(); <span style="">// x=(stream of ("Holo", "Test"), null), the order of the stream is not guarantee</span>
<span style="">
</span><span style=""> // scope.fork(() -> "Holo");
</span><span style=""> // scope.fork(() -> "Test");
</span><span style=""> // scope.fork(() -> {
</span><span style=""> // throw new IllegalStateException();
</span><span style=""> // });
</span><span style=""> // var x = scope.join(); // x=(null, illegalStateException)
</span><span style=""> </span>} <span style="">catch </span>(InterruptedException | StructuredTaskScope.TimeoutException e) {
<span style="">throw new </span>RuntimeException(e); <span
style="">// only teachable on timeout *when the timeout is configured through StructuredTaskScope.open*
</span><span style=""> // To make it so timeout doesn't get into here, add into the `AllSuccessfulResult` type built in mechanism of timeout
</span><span style=""> // implementing timeout inside the Joiner is not hard, but I omitted it from the Proof of Concept
</span><span style=""> </span>}
}
<span style="">record </span>Result<<span style="">V</span>, <span
style="">R </span><span style="">extends </span>Throwable>(<span
style="">V </span><span style="">value</span>, <span style="">R </span><span
style="">exception</span>) {}
<span style="">@SuppressWarnings</span>(<span style="">"preview"</span>)
<span style="">static final class </span>AllSuccessfulResult<<span
style="">T</span>> <span style="">implements </span>StructuredTaskScope.Joiner<<span
style="">T</span>, Result<Stream<<span style="">T</span>>, Throwable>> {
<span style="">private static final </span>MethodHandles.Lookup <span
style="font-style: italic;">lookup </span>= MethodHandles.<span
style="font-style:italic">lookup</span>();
<span style="">private static final </span>VarHandle <span
style="font-style: italic;">FIRST_EXCEPTION</span>;
<span style="">static </span>{
<span style="">try </span>{
<span style="font-style: italic;">FIRST_EXCEPTION </span>= <span
style="font-style: italic;">lookup</span>.findVarHandle(<span
style="font-style: italic;">lookup</span>.lookupClass(), <span
style="">"firstException"</span>, Throwable.<span style="">class</span>);
} <span style="">catch </span>(NoSuchFieldException | IllegalAccessException e) {
<span style="">throw new </span>RuntimeException(e); <span
style="">// shouldn't be possible to reach
</span><span style=""> </span>}
}
<span style="">// list of forked subtasks, only accessed by owner thread
</span><span style=""> </span><span style="">private final </span>List<StructuredTaskScope.Subtask<<span
style="">T</span>>> <span style="">subtasks </span>= <span
style="">new </span>ArrayList<>();
<span style="">private volatile </span>Throwable <span style="">firstException</span>;
<span style="">@Override
</span><span style=""> </span><span style="">public boolean </span><span
style="">onFork</span>(StructuredTaskScope.Subtask<? <span
style="">extends </span><span style="">T</span>> subtask) {
<span style="">if </span>(subtask.state() != StructuredTaskScope.Subtask.State.<span
style="font-style: italic;">UNAVAILABLE</span>) { <span
style="">// after `join` you can't use onFork
</span><span style=""> </span><span style="">throw new </span>IllegalArgumentException(<span
style="">"Subtask not in UNAVAILABLE state"</span>);
}
<span style="">@SuppressWarnings</span>(<span style="">"unchecked"</span>)
<span style="">var </span>s = (StructuredTaskScope.Subtask<<span
style="">T</span>>) subtask;
<span style="">subtasks</span>.add(s);
<span style="">return false</span>;
}
<span style="">@Override
</span><span style=""> </span><span style="">public boolean </span><span
style="">onComplete</span>(StructuredTaskScope.Subtask<? <span
style="">extends </span><span style="">T</span>> subtask) {
StructuredTaskScope.Subtask.State state = subtask.state();
<span style="">if </span>(state == StructuredTaskScope.Subtask.State.<span
style="font-style: italic;">UNAVAILABLE</span>) {
<span style="">throw new </span>IllegalArgumentException(<span
style="">"Subtask has not completed"</span>); <span style="">// this shouldn't be reachable I think, but just in case
</span><span style=""> </span>}
<span style="">return </span>(state == StructuredTaskScope.Subtask.State.<span
style="font-style: italic;">FAILED</span>)
&& (<span style="">firstException </span>== <span
style="">null</span>)
&& <span style="font-style: italic;">FIRST_EXCEPTION</span>.compareAndSet(<span
style="">this</span>, <span style="">null</span>, subtask.exception());
}
<span style="">@Override
</span><span style=""> </span><span style="">public </span>Result<Stream<<span
style="">T</span>>, Throwable> <span style="">result</span>() {
<span style="">if </span>(<span style="">firstException </span>!= <span
style="">null</span>) {
<span style="">return new </span>Result<>(<span
style="">null</span>, <span style="">firstException</span>);
}
<span style="">var </span>results = <span style="">subtasks</span>.stream()
.filter(t -> t.state() == StructuredTaskScope.Subtask.State.<span
style="font-style: italic;">SUCCESS</span>)
.map(StructuredTaskScope.Subtask::get);
<span style="">return new </span>Result<>(results, <span
style="">null</span>);
}
}
</pre>
</div>
</div>
</blockquote>
</body>
</html>