David Kubecka
11/11/2024, 3:45 PMwithAccount
is missing the Raise<Any>
context.
fun <R, E : Any> withApiTraceError(
block: context(Account, Raise<E>) () -> R,
exceptionProducer: (E) -> Exception,
): R {
return withApiError(exceptionProducer) {
withAccount {
block(this@withAccount, this@withApiError)
}
}
}
private fun <R, E : Any> withApiError(
exceptionProducer: (E) -> Exception = { IllegalStateException("") },
block: Raise<E>.() -> R,
): R = recover(block) { error -> throw exceptionProducer(error) }
context(Raise<Any>)
fun <T> withAccount(block: Account.() -> T): T = TODO()
data object Account
Yet, if I use the default exceptionProducer
in withApiError
like this
fun <R, E : Any> withApiTraceError(
block: context(Account, Raise<E>) () -> R,
exceptionProducer: (E) -> Exception,
): R {
return withApiError { // using default here
withAccount {
block(this@withAccount, this@withApiError)
}
}
}
then it compiles fine. What's going on?Youssef Shoaib [MOD]
11/11/2024, 4:07 PME
in withApiTraceError
might not be Any, and so withApiTraceError's exceptionProducer
might not accept Any
, hence you have a Raise<E>
in context instead of a Raise<Any>
The default version works because now withApiError
has no constraints from parameters on its E
, except that its lambda calls withAccount
which expects a Raise<Any>
receiver (more accurately context), and so E
is inferred to be Any
(this is called builder inference)
Are you sure what you want is a Raise<Any>
on withAccount
?David Kubecka
11/11/2024, 5:12 PMexceptionProducer
params (the exception was hardcoded). That worked even with context(Raise<Any>)
on withAccount
because the E
in withApiError
was inferred via builder inference (as you have pointed out). The solution is simply to write
context(Raise<E>)
fun <E : Any, T> withAccount(block: Account.() -> T): T = TODO()
To complete my understanding: If recover
params were not annotated with @BuilderInference
then even my original version (without any exceptionProducer
s) wouldn't compile, probably complaining about "type cannot be inferred", right?Youssef Shoaib [MOD]
11/11/2024, 5:16 PM@BuilderInference
is enabled by default since 1.8? so the annotation does nothing FYI. But before that was implemented yes it would likely complain that there's no info to infer the type (although maybe it could've guessed it as Any simply because that's the most-permissive Raise
)
Your new withAccount
can't actually do anything with the Raise
it's given (because E
is unknown, and hence nothing can be raised). Are you sure you need a Raise
context at all? If you're doing this to force the user to have some Raise
, you should probably use Raise<*>
insteadDavid Kubecka
11/11/2024, 5:22 PMwithAccount
calls another function that needs a Raise context. And that function finally `raise`s something. So I have
context(Raise<E>)
fun <E : Any, T> withAccount(block: Account.() -> T): T = with(getAccount(), block)
context(Raise<NotFound>)
fun getAccount() = findAccount() ?: raise(NotFound)
Does it make sense or is there still a room for improvement?David Kubecka
11/11/2024, 5:23 PMDavid Kubecka
11/11/2024, 5:26 PMApiError
base class and I want to indicate that the whole withApiError
machinery works with some subclass of ApiError
. How should I define the contexts of the functions above?David Kubecka
11/11/2024, 5:32 PMwithApiError
can within its block handle any subclasses of ApiError
(NotFound
is one such subclass).Youssef Shoaib [MOD]
11/11/2024, 5:35 PMwithAccount
needs Raise<NotFound>
. It doesn't need anything with generics because Raise
is declared in
. withApiError
can stay exactly the same. Now, in withAPITraceError
the issue arises that you need a Raise<NotFound>
and a Raise<E>
. The best way to do that is probably something like:
fun <R, E : Any> withApiTraceError(
block: context(Account, Raise<E>) () -> R,
exceptionProducer: (E) -> Exception,
onNotFound: () -> Exception
): R {
return withApiError(exceptionProducer) {
recover({
withAccount {
block(this@withAccount, this@withApiError)
}
}) { _: NotFound -> throw onNotFound() }
}
}
Youssef Shoaib [MOD]
11/11/2024, 5:36 PMwithApiError
must handle all subclasses of ApiError
then just concretize it:
private fun <R> withApiError(
exceptionProducer: (ApiError) -> Exception = { IllegalStateException("") },
block: Raise<ApiError>.() -> R,
): R = recover(block) { error -> throw exceptionProducer(error) }
David Kubecka
11/11/2024, 5:39 PMexceptionProducer
to take the concrete E
subclass but simply any ApiError subclass.
Thanks!