CLOVIS
05/14/2023, 12:12 PMsealed class ParsingError {
object InvalidFormat : ParsingError()
object InvalidArguments : ParsingError()
}
fun parse(…): Either<ParsingError, SomeType> = TODO()
sealed class ComputationError {
data class ParsingError(val error: ParsingError) : ComputationError()
object SomeOtherError : ComputationError()
}
fun computation(…): Either<ComputationError, SomeType> = either {
val a = parse(…)
.mapLeft(ComputationError::ParsingError)
.bind()
…
}
The .mapLeft().bind() is quite verbose and appears a lot… is there some nice API I'm missing to simplify it?
It seems it would be even worse with context receivers, because I would be forced to add an either {} block to all sub-function calls, defeating the purposeYoussef Shoaib [MOD]
05/14/2023, 1:18 PMcontext(Raise<ParsingError>)
fun parse(…): SomeType = TODO()
context(Raise<ComputationError>)
fun computation(…): SomeType {
val a = recover({parse(…)}) {
raise(ComputationError.ParsingError(it)
}
…
}
It allows you to call parse while handling its error by raising another error. You could also do a similar thing with effect or either, but why over complicate things? recover is made to do exactly this, and it allows you to sometimes treat the error as success if you really want toCLOVIS
05/14/2023, 1:19 PMYoussef Shoaib [MOD]
05/14/2023, 1:19 PMparse().bind()CLOVIS
05/14/2023, 1:20 PMrecover({ parse(…).bind() }) {
raise(ComputationError.ParsingError(it))
}.bind()Youssef Shoaib [MOD]
05/14/2023, 1:20 PMparse in your case will return an Either, hence why we need to bind it inside recover, but afterwards recover will just return the value itselfCLOVIS
05/14/2023, 1:21 PMCLOVIS
05/14/2023, 1:25 PMCLOVIS
05/14/2023, 1:26 PMrecover overload that always raised it would be nicer:
recover({ field.validate().bind() }, Form.Failures::InvalidImport)
IMO that looks close to optimal for this caseYoussef Shoaib [MOD]
05/14/2023, 1:29 PMpublic inline fun <OuterError, Error, A> Raise<OuterError>.withMappedErrors(
@BuilderInference errorMapper: (error: Error) -> OuterError,
@BuilderInference block: Raise<Error>.() -> A,
): A = fold(block, { throw it }, { raise(errorMapper(it)) }, ::identity)
Which makes the code like this:
fun computation(…): Either<ComputationError, SomeType> = either {
withMappedErrors(ComputationError::ParsingError) {
val a = parse(…).bind()
…
}
}
And the bind goes away if you use context receivers.
One thing I'm not sure of is if the @RaiseDsl DslContext annotation might prevent you from raising ComputationError inside of the withMappedErrors. If Intellij does complain, you can either suppress the warning, or (with context receivers) make withMappedErrors re-apply the outer Raise context like this:
public inline fun <OuterError, Error, A> Raise<OuterError>.withMappedErrors(
@BuilderInference errorMapper: (error: Error) -> OuterError,
@BuilderInference block: context(Raise<Error>, Raise<OuterError>) () -> A,
): A = fold(block, { throw it }, { raise(errorMapper(it)) }, ::identity)CLOVIS
05/14/2023, 1:31 PMwithMappedErrors function looks great. Should I create a feature request for it?Youssef Shoaib [MOD]
05/14/2023, 1:34 PMResultDsl, so it might have to wait until context receivers are stableCLOVIS
05/14/2023, 1:53 PM