leonhardt
03/21/2025, 8:44 AMleonhardt
03/21/2025, 8:44 AM```context(_: Raise<UserNotFound>)
fun User.isValid(): Unit =
ensure(id > 0) { UserNotFound("User without a valid id: $id") }```However, when using the experimental support for context parameters in Kotlin 2.1.20, this syntax doesn’t compile. The compiler reports
ensure
as an unresolved reference.leonhardt
03/21/2025, 8:44 AMimport arrow.core.raise.Raise
import arrow.core.raise.ensure
data class Invalid(val message: String)
Approach 1:
Adapted from the docs example, but it doesn’t compile. The compiler doesn’t infer that ensure
should be available via the context parameter.
context parameter
context(_: Raise<Invalid>) fun greeting1(personName: String): String {
ensure(personName.isBlank()) { Invalid("Person name is empty") }
return "Hello, $personName"
}
Approach 2a:
Here, the raise
instance is used to call its own raise method explicitly (raise.raise
). This dual usage within the same context can be confusing, as it’s not immediately clear which part of the API is being utilized.
context(raise: Raise<Invalid>) fun greeting2a(personName: String): String {
if (personName.isBlank()) raise.raise(Invalid("Person name is empty"))
return "Hello, $personName"
}
Approach 2b:
This version uses the ensure
helper on the raise
instance. While it avoids the name shadowing, it only helps if you can constrain yourself to the ensure*
helpers.
context(raise: Raise<Invalid>) fun greeting2b(personName: String): String {
raise.ensure(personName.isBlank()) { Invalid("Person name is empty") }
return "Hello, $personName"
}
Approach 3a:
By wrapping the code in a with(raise)
block, you reduce repetition. However, this approach introduces extra verbosity and potential shadowing issues, as the raise
object has the same name as the function being invoked.
context(raise: Raise<Invalid>) fun greeting3a(personName: String): String {
with(raise) {
if (personName.isBlank()) raise(Invalid("Person name is empty"))
return "Hello, $personName"
}
}
Approach 3b:
Similar to 2b but wrapped in a with(raise)
block for slightly improved readability. However, it still relies on constraining yourself to the ensure*
helpers to avoid the name shadowing.
context(raise: Raise<Invalid>) fun greeting3b(personName: String): String {
with(raise) {
ensure(personName.isBlank()) { Invalid("Person name is empty") }
return "Hello, $personName"
}
}
leonhardt
03/21/2025, 8:44 AMAlejandro Serrano.Mena
03/21/2025, 8:55 AMcontext(raise> Raise<Int>)
and call raise.raise(3)
explicitly, or use with(raise)
if you feel that is too much repetitionphldavies
03/21/2025, 8:58 AMleonhardt
03/21/2025, 9:32 AMcontext(r: Raise<Error>) fun <Error> raise(error: Error): Nothing = r.raise(error)
@OptIn(ExperimentalContracts::class)
context(r: Raise<Error>) fun <Error> ensure(condition: Boolean, raise: () -> Error) {
contract {
callsInPlace(raise, AT_MOST_ONCE)
returns() implies condition
}
return if (condition) Unit else r.raise(raise())
}
@OptIn(ExperimentalContracts::class)
context(r: Raise<Error>) fun <Error, B : Any> ensureNotNull(value: B?, raise: () -> Error): B {
contract {
callsInPlace(raise, AT_MOST_ONCE)
returns() implies (value != null)
}
return value ?: r.raise(raise())
}
context(_: Raise<Invalid>) fun greeting4a(personName: String): String {
if (personName.isBlank()) raise(Invalid("Person name is empty"))
return "Hello, $personName"
}
context(_: Raise<Invalid>) fun greeting4b(personName: String): String {
ensure(personName.isBlank()) { Invalid("Person name is empty") }
return "Hello, $personName"
}
context(_: Raise<Invalid>) fun greeting4c(personName: String?): String {
ensureNotNull(personName) { Invalid("Person name is empty") }
return "Hello, $personName"
}
I’ll keep an eye on how this develops. I really appreciate your guidance and the context provided so far. Just to clarify—does Arrow plan to publish bridge functions like these as context parameters reach a stable release, or is that ultimately expected to be handled by the consumer?Alejandro Serrano.Mena
03/21/2025, 9:35 AMphldavies
03/21/2025, 9:41 AMphldavies
03/21/2025, 9:41 AMleonhardt
03/21/2025, 9:53 AM