min
08/26/2026, 6:51 AMmaybeX?.let { x ->
f(x)?.let { y ->
g(x, y)?.let { z ->
h(x, y, z)
}
}
}
?: fallback
I’d like something that’s similar to
val x <- maybeX
val y <- f(x)
val z <- g(x, y)
h(x, y, z)Youssef Shoaib [MOD]
08/26/2026, 7:03 AMimport arrow.core.nullable
nullable { // this : Raise<T?>
val x = maybeX.bind()
val y = f(x).bind()
val z = g(x, y).bind()
h(x, y, z)
} ?: fallback
or in vanilla Kotlin:
run {
val x = maybeX ?: return@run null
val y = f(x) ?: return@run null
val z = g(x, y) ?: return@run null
h(x, y, z)
} ?: fallback
If you're familiar with monads, ? kinda sorta forms a monad (but not exactly because it's not a discriminated union, i.e. Foo?? is the same type as Foo?). Arrow provides these "raise builders" that act as monadic comprehensions.
As you can see, nullable acts like a run<T?> block, but it gives you a Raise<T?> that acts like a return@run wrapped in a call.min
08/26/2026, 7:29 AMtry and exclude ones that have bubbled up?
return try {
val nestedObject = context!!
.takeIf { callableId.callableName == COMPANION_METHOD_TO_GENERATE }!!
.owner
val outerClass = nestedObject
.let(this::outerClassIfApplicable)!!
listOf((FirNamedFunction::symbol)(this.createMemberFunction(
owner = nestedObject,
name = COMPANION_METHOD_TO_GENERATE,
returnType = outerClass.constructType(),
key = KEY,
) {
val functionBuilder = this
functionBuilder.status {
val statusBuilder = this
statusBuilder.isOperator = true
}
@OptIn(
SymbolInternals::class,
DirectDeclarationsAccess::class,
)
outerClass.fir
.declarations
.filterIsInstance<FirConstructor>()
.firstOrNull(FirConstructor::isPrimary)!!
.valueParameters
.forEach { parameter ->
functionBuilder.valueParameter(
name = parameter.name,
type = parameter.returnTypeRef.coneType,
)
}
}))
} catch (_: NullPointerException) {
emptyList()
}CLOVIS
08/26/2026, 7:30 AMmin
08/26/2026, 7:31 AMarrow.core.nullable work?CLOVIS
08/26/2026, 7:35 AMCLOVIS
08/26/2026, 7:38 AMimplementation dependency so it won't affect other modulesYoussef Shoaib [MOD]
08/26/2026, 7:42 AMclass ControlException(val raise: Raise): Exception()
class Raise {
fun raise(): Nothing = throw ControlException(this)
fun <T: Any> T?.bind(): T = this ?: raise()
}
inline fun <T> nullable(block: Raise.() -> T): T? = with(Raise()) {
try { block() } catch(e: ControlException) { if (e.raise === this) null else throw e }
}
This makes Raise have "static" or "lexical" semantics, i.e. you can look at a raise call and know exactly where it'll be caught (unless someone is indiscriminately swallowing Exceptions).
The implementation in Arrow is safer (detects "late" raise calls, extends CancellationException for coroutines compatibility), more performant (it turns the stack trace off by default), and has a lot of QoL features (too many to name)min
08/28/2026, 9:08 AM