Using Raise via context parameter isn't (currently...
# arrow-contributors
p
Using Raise via context parameter isn't (currently) as pleasant as via receivers unfortunately.
Copy code
import arrow.core.raise.Raise
import arrow.core.raise.either

context(r: Raise<String>)
fun doSomething(): Unit = TODO()

context(r: Raise<Int>)
fun doSomething(): Unit = TODO()

fun main() {
  either<String, Unit> {
    either<Int, Unit> {
      doSomething() // Overload resolution ambiguity between candidates:
                    // context(r: Raise<String>) fun doSomething(): Unit
                    // context(r: Raise<Int>) fun doSomething(): Unit
    }
  }
}
whereas declaring with
Copy code
fun Raise<String>.doSomething(): Unit = TODO()
fun Raise<Int>.doSomething(): Unit = TODO()
resolves correctly - but this doesn't work for cases where you already have a receiver, obviously. Even labelling the closest
either
with
intRaise@{
and wrapping with
context(this@intRaise) { doSomething() }
doesn't resolve it. Neither does attempting to use a contextual alternative
either
constructor:
Copy code
inline fun <Error, A> either(block: context(Raise<Error>) () -> A): Either<Error, A> = arrow.core.raise.either(block)
y
That's definetly a Kotlin issue I believe. Likely better for #CQ3GFJTU1. I do agree that this should be fixed.
p
Agreed, I'll try and rephrase the question without the arrow focus later if the kids give me a chance 😅 It's also a concern for arrow mostly with the https://github.com/arrow-kt/arrow/pull/3606 context-parameter bridges impacted. (
ensureNotNull
will fail to resolve without explicit type parameter to disambiguate when used with nested/multiple contexts)
a
thanks for the heads up! I'll take this into the Kotlin Team, and see what we can do about this
but tbh, the design of context parameters assumes that you won't need this kind of disambiguation, in that context parameters should not "drive" the resolution, but rather being "additonal contexts" required for the function which doesn't mean that you're example is a no-go, I'm just saying that there's a deliberate design choice there to make context resolution more lightweight at the expense of these ambiguities
p
Unfortunately with context resolution in this current state it does mean that any usage of
withError
or nested
recover
etc will be awkward to use.
a
this is if we were to replace everything with parameters; but
either
,
option
, and all those runners still introduce
Raise
as a receiver, so the member version would be preferred
withError
and
recover
also have additional type arguments which can be used to guide the resolution
so this problem is only really really bad if you have several function with the same name which require context parameters of different types the case of a single generic function can be disambiguated in most cases
p
I've encountered this when playing with the ktor raise server module when trying a method that takes a
context(Raise<Response>, Raise<RequestError>) RoutingContext.() -> Unit
lambda - I can see a lot of users wanting to have custom scope functions that provide
Raise
via context. It's not unworkable with explicit type args though: https://github.com/tKe/arrow/pull/2/commits/1a891a3f89bdfbddeb9e1b88331337288f145165#diff-ae4a65448c25309856d1b62cf[…]61a7a7ff0ceda7d319ec1388b6L124
c
this is if we were to replace everything with parameters; but
either
,
option
, and all those runners still introduce
Raise
as a receiver, so the member version would be preferred
But is that a good thing? Will future APIs continue to have
Raise
as a receiver? To me, it seems new APIs should use context parameters, as those better describe what
Raise
is.