Error handling using extensible types of disjunction (union) (as on the left side of Either)?

I am currently brainstorming and exploring the best way to explicitly handle error handling in scala.

Ideal end product:

  • The compiler guarantees that you have checked all (explicitly indicated) errors.
  • As few templates as possible
  • There is no or a bit of additional execution cost (which means the absence of a parent type of all possible errors from all functions)
  • Allows you to be explicit and implicit by pointing

Basically, I want to check exceptions on steroids .

Ignoring throwing exceptions, a typical way to handle this is to use the Either type (or \/ from Scalaz, which I use) and have the left side as ADT containing all possible errors for example:

 sealed trait Error_parseAndValidate case class ParseError(msg: String) extends Error_parseAndValidate case class ValidateError(msg: String) extends Error_parseAndValidate def parseAndValidate(str: String): Error_parseAndValidate \/ Int = { // can return ParseError or ValidateError } 

However, this becomes very tedious if the function call has several levels:

Consider this example database with the following call stack

main β†’ getResultWithString β†’ parseAndValidate / fetchResultFromDB

 // error type ADT containing expected errors in parseAndValidate // similarly for other error_* traits sealed trait Error_parseAndValidate case class ParseError(msg: String) extends Error_parseAndValidate case class ValidateError(msg: String) extends Error_parseAndValidate def parseAndValidate(str: String): Error_parseAndValidate \/ Int = { // can return ParseError or ValidateError } sealed trait Error_fetchResultFromDB case class DBError(msg: String) extends Error_fetchResultFromDB def fetchResultFromDB(lookupId: Int): Error_fetchResultFromDB \/ Result = { // can return DBError } sealed trait Error_getResultWithString case class ErrorFromFetchResult(Error_fetchResultFromDB) extends Error_getResultWithString case class ErrorFromParseValidate(Error_parseAndValidate) extends Error_getReusltWithString def getResultWithString(input: String): Error_getResultWithString \/ Result = { } // we need to 'extract/unwrap' our error type. this is tedious! def main() = { getResultWithString.leftMap { case ErrorFromFetchResult(e) => e match { case ParseError => case ValidateError => } case ErrorFromParseValidate(e) => e match { case DBERror => } } } 

Although we have achieved the correctness, it is tiring, especially if you have more than 3-4 levels of the call stack!

What I represent is an extensible type of disjunction that will allow us to β€œexpand” our mistakes. It seems that something in shapelss can help me achieve this, but have not used it in the past and I do not think that HList is the right tool for this situation.

This is what I foresaw:

I use symbol | here to represent here the disjunction (union) between the two types. The most important thing here is that we no longer need to wrap and deploy our errors using ADT. The mistake we finally end up with is just a flat disjunction *.

 // you can see here we are simply 'joining' the errors from both method calls, // instead of wrapping it in a ADT which requires unwrapping def getResultWithString(input: String): Error_parseAndValidate|Error_fetchResultFromDB \/ Result = { for { lookupId <- parseAndValidate(input) result <- fetchResultFromDB(lookupId) } yield result } type Error_parseAndValidate = ParseError | ValidateError def parseAndValidate(input: String): Error_parseAndValidate \/ Int = { //returns ParseError or ValidationError } type Error_fetchResultFromDB = DBError def fetchResultFromDB(lookupId: Int): Error_fetchResultFromDB \/ Result = { //returns DBError } def main() = { getResultWithString("bad string").leftMap { case ParseError => //... case ValidateError => //... case DBError => //... } } 

Answer from this. The SO question has some solutions, however the solutions are not extensible.

My question

Is it possible to achieve what I represent in scala?

+6
source share

All Articles