<!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>