Lionel Briand
09/21/2026, 4:17 PMinline infix fun <L, R> Either<L, R>.requireLeftOr(value: L): L = when (this) {
is Left -> left
is Right -> value
}
And later I want to use it like this:
val value = run {
val result = either requireLeftOr return@run 0
result * 2
}
Technically, if the function is inlined, the return statement is never called.
But from my testing it does not give this result and instead always returns.min
09/22/2026, 7:29 AMinstead always returns.That’s because the call
either requireLeftOr return@run 0
1. evaluates either
2. evaluates return@run 0
3. and passes the results of steps 1 and 2 to Either#requireLeftOr.
But your step 2 short circuits and exits early, which is why step 3 never runs.
sealed class Either<out L, out R> private constructor() {
class Foo<out L>(val foo: L): Either<L, Nothing>()
class Bar<out R>(val bar: R): Either<Nothing, R>()
fun asFoo(): L? { return (this as? Either.Foo)?.foo }
}
fun main() {
println("[main] start")
run {
val x = Either.Foo(42).asFoo() ?: return@run
println("x = $x")
}
run {
val y = Either.Bar("").asFoo() ?: return@run
println("y = $y")
}
run {
val z = Either.Foo(67).asFoo() ?: return@run
println("z = $z")
}
println("[main] end")
}
This behaves the way you expect it to. Unfortunately though, ?: doesn’t let you specify where the coalescent ends. That means you probably want to additionally declare a method such as asFooOr(f: () -> L) for ergonomics in call chains.
either
.asFoo()
?: return@run 0
// ❌ `something` isn't called on the `asFoo` return value.
.something()
either
.asFooOr { return@run 0 }
.something()min
09/22/2026, 7:32 AMinline fun <T: Any> T?.coalesce(fallback: () -> T): T {
return this ?: fallback()
}
With it, I don’t have to declare asFooOr and the like for every type that needs an operation like it.
either.asFoo().coalesce { return@run 0 }.something()CLOVIS
09/22/2026, 7:43 AMinline infix fun <L, R> Either<L, R>.leftOrElse(default: () -> L): L = when (this) {
is Left -> left
is Right -> default()
}
run {
val x = Either.Left(42).leftOrElse { return@run }
println("x = $x")
}
But also, this pattern is provided by #C5UPMM0A0 out of the box:
singleton {
ignoreErrors {
val x = Either.Right(42).bind()
println("x = $x")
}
}
though note that Arrow's Either is biased towards the right (right = success, left = failure), so it behaves symmetrically to your example.min
09/22/2026, 7:54 AMleftOrElse is a good convenience method to provide in the API but doesn’t obviate coalesce IMO. It lets you improvise an orElse variant of every nullable method on the fly.CLOVIS
09/22/2026, 8:17 AMasFoo() method would idiomatically be called fooOrNull() and if you have that you can just write
(either.leftOrNull() ?: return@run 0).something()
The coalesce function exists solely to avoid the parenthesesmin
09/22/2026, 9:18 AMmaybeAnnotatedClass
.getAnnotationByClassId(
classId = ANNOTATION_CLASS,
session = session,
)
.coalesce { return Either.Bar(ParseError.ClassNotAnnotated) }
.getKClassArgument(ANNOTATION_ARG)
.coalesce { return Either.Bar(ParseError.ArgumentMissing) }
.classId?.takeIf(this::isValidArgument)
.coalesce { return Either.Bar(ParseError.ArgumentInvalid(maybeAnnotatedClass)) }
Yes, and I want to avoid the parentheses almost every time I have to handle nullability to write fluent call chains.Lionel Briand
09/22/2026, 8:01 PMinline fun <L, R> Either<L, R>.requireLeft(
block: (Right<R>) -> Nothing,
): L
I will stick to the lambda way of handling the erroneous state when left (or right) isn't OK.