使用可扩展析取(并集)类型的错误处理(作为Either的左侧)?

Jac*_*ang 6 error-handling macros scala shapeless

我目前正在集思广益,研究在scala中进行显式错误处理的最佳方法.

理想的最终产品:

  • 编译器强制您已检查所有(明确声明的)错误
  • 尽可能少的样板
  • 没有或几乎没有额外的运行时成本(这意味着没有来自所有函数的所有可能错误的父类型)
  • 允许您在陈述中明确和隐含

基本上,我想在steriods上检查异常.

忽略异常抛出,处理这种情况的典型方法是使用Either类型(或\/来自我正在使用的Scalaz),并将left侧面作为包含所有可能错误的ADT,如下所示:

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
}
Run Code Online (Sandbox Code Playgroud)

但是,如果函数调用嵌套多个级别,这将变得非常繁琐:

考虑具有以下调用堆栈的此DB示例

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 => 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然我们已经达到了正确性,但是这很乏味,特别是当你有超过3-4级的调用堆栈时!

我所设想的是一种可扩展的析取类型,它允许我们"扩展"我们的错误.感觉有些东西shapelss可以帮助我实现这一目标,但过去没有使用它,我认为HList这种情况不适合.

这就是我所设想的:

我在|这里使用符号来表示两种类型之间的分离(联合).这里最重要的是我们不再需要使用ADT来包装和解包我们的错误 - 我们最终匹配的错误只是一个**平面分离*.

// 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 => //...
    }
}
Run Code Online (Sandbox Code Playgroud)

这个 SO问题的答案有一些解决方案,但解决方案不可扩展.

我的问题

是否有可能实现我在scala中的环境?