<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p><font size="4">For clarity, this line of thought originally arose
from questions around outcome aggregation and failure
representation in the context of Loom’s StructuredTaskScope,
particularly when reasoning about multiple concurrent subtasks
and their combined results. The sketch above is not intended to
suggest changes to Loom or to structured concurrency APIs, but
simply reflects how thinking through that specific context led
me to explore more general value-oriented outcome modeling as a
way to reason about the trade-offs discussed.<br>
<br>
I’m not proposing this as a solution, nor suggesting that such
an abstraction should exist; this was simply a way for me to
think more concretely about the design space and the trade-offs
discussed in the thread. If nothing else, I found the exercise
useful for clarifying where value-oriented outcomes align well
with Java’s existing idioms, and where they introduce tension.
I’ll defer to the architects’ judgment on whether any of this is
worth revisiting now or in the future, and appreciate the
perspectives shared in helping me better understand the
constraints involved.</font></p>
<p><font size="4">Note: I am content with these </font><font
size="5"><b><i>not</i></b></font><font size="4"> being
extensible by mere mortals, but maintained and extended through
the exclusive diligence of the Java Language Architects.</font></p>
<p><font size="5">Even when using value-oriented outcomes such as
Result, callers must still be prepared for exceptions to be
thrown, whether due to programming errors, contract violations,
cancellation, or JVM-level conditions. This sketch is not
intended to eliminate or replace Java’s exception model, but to
complement it where expected failure can be modeled explicitly
as data.</font></p>
<blockquote>
<p><font size="4">package java.util;<br>
<br>
import java.util.function.Function;<br>
<br>
public sealed interface Result<V, F><br>
permits Result.Success, Result.Failure {<br>
<br>
// --- Variants ---<br>
<br>
record Success<V, F>(V value) implements
Result<V, F> {}<br>
record Failure<V, F>(F failure) implements
Result<V, F> {}<br>
<br>
// --- Constructors ---<br>
<br>
static <V, F> Result<V, F> success(V value) {<br>
return new Success<>(value);<br>
}<br>
<br>
static <V, F> Result<V, F> failure(F failure)
{<br>
return new Failure<>(failure);<br>
}<br>
<br>
// --- Introspection ---<br>
<br>
default boolean isSuccess() {<br>
return this instanceof Success<?, ?>;<br>
}<br>
<br>
default boolean isFailure() {<br>
return this instanceof Failure<?, ?>;<br>
}<br>
<br>
// --- Core transforms ---<br>
<br>
default <U> Result<U, F> map(Function<?
super V, ? extends U> mapper) {<br>
return switch (this) {<br>
case Success<V, F>(var value) -><br>
Result.success(mapper.apply(value));<br>
case Failure<V, F>(var failure) -><br>
Result.failure(failure);<br>
};<br>
}<br>
<br>
default <G> Result<V, G>
mapFailure(Function<? super F, ? extends G> mapper) {<br>
return switch (this) {<br>
case Success<V, F>(var value) -><br>
Result.success(value);<br>
case Failure<V, F>(var failure) -><br>
Result.failure(mapper.apply(failure));<br>
};<br>
}<br>
<br>
default <U> Result<U, F> flatMap(<br>
Function<? super V, ? extends Result<U,
F>> mapper) {<br>
return switch (this) {<br>
case Success<V, F>(var value) -><br>
mapper.apply(value);<br>
case Failure<V, F>(var failure) -><br>
Result.failure(failure);<br>
};<br>
}<br>
<br>
// --- Extraction ---<br>
<br>
default V orElse(V fallback) {<br>
return switch (this) {<br>
case Success<V, F>(var value) -> value;<br>
case Failure<V, F>(var failure) ->
fallback;<br>
};<br>
}<br>
<br>
default V orElseGet(Function<? super F, ? extends V>
fallback) {<br>
return switch (this) {<br>
case Success<V, F>(var value) -> value;<br>
case Failure<V, F>(var failure) ->
fallback.apply(failure);<br>
};<br>
}<br>
<br>
default <X extends Throwable> V orElseThrow(<br>
Function<? super F, ? extends X>
toException) throws X {<br>
return switch (this) {<br>
case Success<V, F>(var value) -> value;<br>
case Failure<V, F>(var failure) -><br>
throw toException.apply(failure);<br>
};<br>
}<br>
<br>
// --- Optional interop ---<br>
<br>
static <T, F> Result<T, F>
fromOptional(Optional<T> optional, F failureIfEmpty) {<br>
return optional<br>
.<Result<T,
F>>map(Result::success)<br>
.orElseGet(() ->
Result.failure(failureIfEmpty));<br>
}<br>
<br>
static <T, F> Result<T, F> requirePresent(<br>
Result<Optional<T>, F> result,<br>
F failureIfEmpty) {<br>
<br>
return result.flatMap(opt -><br>
opt.<Result<T,
F>>map(Result::success)<br>
.orElseGet(() ->
Result.failure(failureIfEmpty)));<br>
}<br>
<br>
// --- Exception bridge ---<br>
<br>
@FunctionalInterface<br>
interface ThrowingSupplier<T> {<br>
T get() throws Throwable;<br>
}<br>
<br>
static <T> Result<T, Throwable>
catching(ThrowingSupplier<T> supplier) {<br>
try {<br>
return Result.success(supplier.get());<br>
} catch (Throwable t) {<br>
return Result.failure(t);<br>
}<br>
}<br>
<br>
// --- Lossy projections ---<br>
<br>
default Optional<V> success() {<br>
return switch (this) {<br>
case Success<V, F>(var value) ->
Optional.ofNullable(value);<br>
case Failure<V, F>(var failure) ->
Optional.empty();<br>
};<br>
}<br>
<br>
default Optional<F> failure() {<br>
return switch (this) {<br>
case Success<V, F>(var value) ->
Optional.empty();<br>
case Failure<V, F>(var failure) ->
Optional.ofNullable(failure);<br>
};<br>
}<br>
}</font></p>
</blockquote>
<p><font size="4">example</font></p>
<blockquote>
<p><font size="4">switch (result) {<br>
case Result.Success(var value) -> use(value);<br>
case Result.Failure(var failure) -> handle(failure);<br>
}</font></p>
</blockquote>
<p><font size="4">Notes:</font></p>
<ul>
<li><font size="4">The name java.util.Result may collide with the
existing java.xml.transform.Result (formerly
javax.xml.transform.Result). This is not a hard conflict, but
it does introduce potential import ambiguity and would need to
be weighed.</font></li>
<li><font size="4">The example switch uses record patterns and
pattern matching for switch, which assumes availability of the
relevant language features (currently preview /
release-dependent).</font></li>
<li><font size="4">Null handling is unspecified. As written, both
Success(null) and Failure(null) are possible, and the lossy
projections (success() / failure()) would treat null as
absence. A real API would likely want to either forbid nulls
or explicitly document this behavior.</font></li>
<li><font size="4">The instance methods success() and failure()
(lossy projections to Optional) share names with the static
factory methods success(V) / failure(E). This is legal but may
be a source of confusion and could merit different naming.</font></li>
<li><font size="4">The catching helper currently captures all
Throwable, including Error. This is intentional for
discussion, but in a real API it would likely raise questions
about whether to catch Exception only, or to offer separate
variants.</font></li>
<li><font size="4">The ThrowingSupplier interface is included as a
convenience placeholder. The JDK may or may not want to
introduce an additional throwing functional interface in
java.util.</font></li>
<li><font size="4">Providing mapFailure naturally raises questions
about whether complementary recovery helpers (e.g., recover,
recoverWith) should exist. They are intentionally omitted here
to keep the surface area minimal.</font></li>
<li><font size="4">The Optional interop helpers (fromOptional,
requirePresent) are intended to clarify the distinction
between “operation failed” and “operation succeeded but value
is absent,” rather than to suggest a particular modeling
style.</font></li>
</ul>
<p><font size="4">Cheers, Eric</font></p>
<br>
</body>
</html>