Michael Friend
07/01/2025, 6:30 PMblock: context(Raise<Error>) () -> T
parameter, calling the function within an either
builder causes some ambiguity between the extension function raise variants like Raise<E>.raise()
you can call on the receiver on the either
block and the context(Raise<E>) raise()
variant available on the context parameter on block which makes it easy to raise an error on the either block rather than within the block parameterMichael Friend
07/01/2025, 6:31 PMcontext(_: Raise<E>)
inline fun <E, T> withLoggingRaise(
logger: Logger,
context: String,
operation: context(Raise<E>) () -> T,
) = either {
logger.operationStart(context)
operation()
}.fold(
ifLeft = {
logger.operationFailure(context, it.toString())
raise(it)
},
ifRight = {
logger.operationSuccess(context)
it
},
)
val x = either {
withLoggingRaise(logger, "operation") {
// If you select the non context param version of this for autocomplete and/or
// dont explicitly import it separately, this will be the `Raise<E>.raise()
// variant using `this` from either block instead of context(Raise<E>) from withLoggingRaise
// which will prevent the things in fold from being called
raise("error")
// Doing this or making sure to add the correct import will raise from the `operation` block in withLoggingRaise
com.example.mrf.raise("error")
}
}
Michael Friend
07/01/2025, 6:48 PMRaiseContext
is in the same package as Raise.raise()
, theres actually no way to force the compile to use the raise function on the context parameter of the block with the fully qualified name like in the above example and the compiler seems to always defer to the extension function on the outer either
block, so maybe somewhat of an edge case error on the context param implementation since i would assume it should use the closest receiver or context parameter when resolving function ambiguity?Michael Friend
07/01/2025, 8:36 PMoperation: context(Raise<E>)
to operation: Raise<E>.()->T
makes it so only the extension function variant is available within the lambda so it removes all the function resolution ambiguity which ensures raise(...)
will always be called on the innermost available raise context. Assuming thats the reason withError
in that PR uses block: Raise<OtherError>.()
rather than block: context(Raise<OtherError>)
. Ultimately feels like mostly a language issue of extension functions taking priority over context parameter functions when trying to decide what function to call