我目前正在集思广益并研究在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中的环境?