3 ms·
Without algebraic types in Scala, it is unfortunately very difficult to have an Either that enumerates all the expected types of exceptions from a block of code
by azernik 11y ago
Without algebraic types in Scala, it is unfortunately very difficult to have an Either that enumerates all the expected types of exceptions from a block of code (especially one that you did not write).
My general idiom for restricting the return types of Try is to recover or recoverWith only the types I consider "normal" and expected. e.g.
finalTryValue.recoverWith {
case FileNotFoundException => Success(HttpResponse(InternalServerError))
case JsonParseError => Success(HttpResponse(BadRequest))
case e: ComplicatedException where e.meetsSomePredicate => Failure(new MoreInformativeException(cause=e))
}
// at this point, if the Try is still a failure, something's wonky and we *should* crash, or do whatever our generic error-handling code specifies
// and the failure will still contain the original stack at the time of failure
With regard to for comprehensions, I just find the Either left/right convention hard to read and remember; I'd even like Try if it was just a thin wrapper that added handy names to Either, like "type Try[E, V] = Either[E <: Throwable, V]" with handy aliases for left and right.
- bad_user 11y agoDon't get me wrong, I do find Try useful. We are talking about public APIs, as in APIs meant to be consumed by people other than the author of that function. The problem with a Try[T] is that if somebody else reads the signature of `def somethingDangerous()`, he won't be able to see that the code can throw a FileNotFoundException or a JsonParseError. Scala does have algebraic types and you can even handle the unexpected with a type like this: sealed trait Error case class FileNotFoundError(f: File) extends Error case class JsonParseError(path: JsPath) extends Error // ... we can even handle the unexpected case class UnknownError(ex: Throwable) extends Error Surely that's extra boilerplate, but if the function is public (meant to be used by other people), then it's worth it. Because now when you say that your function returns an `Either[Error, A]`, this type explicitly documents the important error types that can happen (and are hence not exceptional). I know what you mean by Either. What you want is a right biased Either. Many people feel that way. You've got it implemented in libraries such as Cats or Scalaz. The left/right convention isn't hard to remember, because it's consistent. In general you'll see that types are biased to the right, which really means that the useful happy path result is usually at the right.
- azernik 11y agoSure, if you're willing to create a sealed trait, and separate exception classes, for every function that wants to do this. What this really needs to be usable is union (sum) types So in this case: def dangerousFunction(): Either[FileNotFoundError | JsonParseError, Int] = { ??? } ...and looking for this online, it turns out this is a feature in the experimental Dotty compiler. Yay. And yeah, right-biased either would get halfway there. But it's really the terminology and naming that I have an issue. If I could get an Either knockoff that just called its left and right "error" and "success", that would go a long way to readability.