Posted this as a <comment on the PR >for raise con...
# arrow
m
Posted this as a comment on the PR for raise context param support but figured id crosspost here to start a conversation and see if anyone has workaround ideas. TLDR with an inline function that takes a
block: 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 parameter
Copying the example here for easier visibility
Copy code
context(_: 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") 
    }
}
After some experimentation within the arrow repo ive noticed that since
RaiseContext
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?
Found a workaround for this. Changing the parameter from
operation: 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