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!