Hello, I don't know if its the right canal to di...
# general-advice
l
Hello, I don't know if its the right canal to discuss. Is it possible to create inlined infix function without a lambda? Let's say that I have:
Copy code
inline 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:
Copy code
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.
m
instead 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.
Copy code
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.
Copy code
either
    .asFoo()
    ?: return@run 0
// ❌ `something` isn't called on the `asFoo` return value.
    .something()

either
    .asFooOr { return@run 0 }
    .something()
Personally I get around this problem by using this extension function:
Copy code
inline 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.
Copy code
either.asFoo().coalesce { return@run 0 }.something()
c
@min’s analysis is correct, but I think it would be more idiomatic to have a function
Copy code
inline 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:
Copy code
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.
m
leftOrElse
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.
c
It's not really needed though. Your
asFoo()
method would idiomatically be called
fooOrNull()
and if you have that you can just write
Copy code
(either.leftOrNull() ?: return@run 0).something()
The
coalesce
function exists solely to avoid the parentheses
m
Copy code
maybeAnnotatedClass
    .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.
l
Thanks @min, @CLOVIS for the detailed answer. I just wanted to know if it was something possible and it seems like a clear no. The coalesce method is nice, but it coerce the type to null, which can be useful depending on the context. I already have this method in my codebase :
Copy code
inline 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.