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

时间:2015-11-22 06:45:32

标签: scala error-handling macros 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
}

然而,如果你的函数调用嵌套多个级别,这会变得非常繁琐:

考虑这个带有以下调用堆栈的数据库示例

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 => 
        }
    }
}

虽然我们已经达到了正确性,但是当你有超过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 => //...
    }
}

来自this SO问题的答案有一些解决方案,但解决方案不可扩展。

我的问题

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

0 个答案:

没有答案