-
Notifications
You must be signed in to change notification settings - Fork 451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Raise DSL does not recover when using an interface with Raise<E> #3126
Comments
Hey @cldfzn, Thank you for the bug report, and using Arrow
Was this working for you in a previous version, if yes. Which version did this work for you, and in which version did you encounter regression?
I'm not sure I understand, can you give an example of usage? The actual and expected result? |
Updated the above to hopefully illustrate the problem better. I'm not sure what interactions are occurring here, I'm trying to create a minimal test to reproduce but haven't been able to replicate the behavior yet. |
Ah, I think I see the issue. Since I wrap raise to create my own contextual class, the below referential checks are going to fail in some cases, due to @PublishedApi
@Suppress("UNCHECKED_CAST")
internal fun <R> CancellationException.raisedOrRethrow(raise: DefaultRaise): R =
when {
this is RaiseCancellationExceptionNoTrace && this.raise === raise -> raised as R
this is RaiseCancellationException && this.raise === raise -> raised as R
else -> throw this
} |
This is a standalone test: import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.recover
import io.kotest.assertions.arrow.core.shouldBeRight
import io.kotest.core.spec.style.BehaviorSpec
class RaiseSpec :
BehaviorSpec({
Given("a function returning the recover value") {
val result = either<Error, Unit> {
val env = object : HasInitialWithError<Error> by HasInitialWithError(this, "data") {}
with(env) {
recover({ enhance(context) { update("") } }) { _: Error -> "recovered" }
}
}
Then("the result should be from the recovery function") { result.shouldBeRight("recovered") }
}
})
val context: OperationsContext<String, Error> =
OperationsContext(fn = { raise(SomeError) }, exceptionHandler = { SomeError })
fun interface ExceptionHandler<E> {
operator fun invoke(throwable: Throwable): E
}
fun <ENV, T, E> ENV.update(model: T): String where ENV : HasOperationsWithError<T, E> =
with(context) { Either.catch { fn(model) }.mapLeft { exceptionHandler(it) }.bind() }
sealed interface Error
object SomeError : Error
interface HasInitialWithError<E> : Raise<E> {
val data: String
companion object {
operator fun <E> invoke(raise: Raise<E>, data: String): HasInitialWithError<E> =
object : HasInitialWithError<E>, Raise<E> by raise {
override val data: String = data
}
}
}
interface HasOperations<T, E> {
val context: OperationsContext<T, E>
companion object {
operator fun <T, E> invoke(context: OperationsContext<T, E>): HasOperations<T, E> =
object : HasOperations<T, E> {
override val context: OperationsContext<T, E> = context
}
}
}
interface HasOperationsWithError<T, E> : HasOperations<T, E>, Raise<E> {
companion object {
operator fun <T, E> Raise<E>.invoke(context: OperationsContext<T, E>): HasOperationsWithError<T, E> =
object : HasOperationsWithError<T, E>, Raise<E> by this {
override val context: OperationsContext<T, E> = context
}
}
}
class OperationsContext<T, E>(
val fn: HasOperationsWithError<T, E>.(T) -> String,
val exceptionHandler: ExceptionHandler<E>
)
suspend fun <ENV, T, VAL, E> ENV.enhance(
context: OperationsContext<T, E>,
block: suspend HasOperationsWithError<T, E>.() -> VAL
): VAL where ENV : HasInitialWithError<E>, ENV : Raise<E> =
with(object : HasOperationsWithError<T, E>, HasOperations<T, E> by HasOperations(context), Raise<E> by this {}) {
block()
} |
Thanks for sharing the reproducible test! Your use-case is something I originally wanted to support, and I think I've used in some cases but perhaps I encoded it slightly different. I need to make some time to properly wrap my head around it and look into it. We originally worked with a |
FWIW, #3052 ran into the same issue, and the fix was to define custom |
Yeah, I’ve worked around the issue. I’ve begun converting the code base to use context receivers as most tooling blockers have been resolved. Using receivers is just so ergonomic I’m less worried about a solution for this problem for myself. Would be interesting to know if there is a nice solution in the end though. |
As per #3334, this feature isn't possible, even if we overhauled |
I have a code base that is using interfaces as contexts (waiting for proper support from tooling, etc.. before using context receivers). When attempting to enhance a trait to run with the Raise DSL, recovery no longer works as expected.
Is this expected behavior or am I doing something wrong?
The text was updated successfully, but these errors were encountered: