Scalaz 7.2.18中ValidationNel使用报错,求解决方案
背景
我尝试用Scalaz 7.2.18做数据验证,写了下面的代码,但编译时抛出了类型不匹配的错误,想请教下问题出在哪以及怎么解决。
我的代码实现
验证逻辑
def hasDob: ValidationNel[AdtError, String] = enc.dob.map(_.success).getOrElse(MissingDateOfBirth(enc).failureNel) def hasAdmitDt: ValidationNel[AdtError, Timestamp] = enc.admitDT.map(_.success).getOrElse( MissingAdmitDate(enc).failureNel ) def hasTimezone: Validation[AdtError, DateTimeZone] = fac.timezone.map(_.success).getOrElse( UndefinedTimezone(fac).failureNel ) (hasDob |@| hasAdmitDt |@| hasTimezone) { (dob, admitTime, facilityTz) => ... }
错误定义与数据模型
case class MissingDateOfBirth(enc: EncAdt) extends AdtError { def logString = s"Txn ID: ${enc.transactionID} Missing DOB: ${enc.accountNumber}" } case class MissingAdmitDate(enc: EncAdt) extends AdtError { def logString = s"Txn ID: ${enc.transactionID} Missing Admit Date: ${enc.accountNumber}" } // 补充的数据模型 case class EncAdt(transactionID: Long, dob: Option[String], admitDT: Option[Timestamp], ... ) case class Hl7Facility( timezone: Option[DateTimeZone], ... )
编译错误信息
Type mismatch: Validation[Nothing, String], actual: scalaz.ValidationNel[MissingDateOfBirth, Nothing]
Type mismatch: Validation[Nothing, String], actual: scalaz.ValidationNel[MissingAdmitDate, Nothing]
Type mismatch: Validation[Nothing, String], actual: scalaz.ValidationNel[UndefinedTimezone, Nothing]
问题分析
这几个错误本质是类型不一致导致的,主要有两个关键点:
Validation和ValidationNel不兼容:你写的hasTimezone返回的是普通Validation,但另外两个函数返回的是ValidationNel(带NonEmptyList的验证类型)。Scalaz的|@|(Applicative Builder)要求所有参与组合的验证必须是同一种类型,错误通道得统一是NonEmptyList[E]才行。- 错误类型的自动推导失效:在
getOrElse里,你直接返回了具体错误子类的failureNel,Scalaz有时候没法自动把这些子类推导成父类AdtError,导致类型不匹配。
解决方案
1. 统一所有验证函数的返回类型为ValidationNel
先把hasTimezone改成返回ValidationNel[AdtError, DateTimeZone],注意要用successNel代替success,这样成功通道也符合Nel的类型:
def hasTimezone: ValidationNel[AdtError, DateTimeZone] = fac.timezone.map(_.successNel).getOrElse(UndefinedTimezone(fac).failureNel)
2. 显式指定错误类型,帮助Scalaz推导
在failureNel后面显式指定父类AdtError,避免Scalaz把错误类型锁定在具体的子类上:
def hasDob: ValidationNel[AdtError, String] = enc.dob.map(_.successNel).getOrElse(MissingDateOfBirth(enc).failureNel[AdtError]) def hasAdmitDt: ValidationNel[AdtError, Timestamp] = enc.admitDT.map(_.successNel).getOrElse(MissingAdmitDate(enc).failureNel[AdtError])
这里用successNel是因为它返回的是ValidationNel[Nothing, T],和getOrElse里的ValidationNel[AdtError, Nothing]组合后,Scalaz会自动合并成我们需要的ValidationNel[AdtError, T]。
3. 修正后的完整代码
def hasDob: ValidationNel[AdtError, String] = enc.dob.map(_.successNel).getOrElse(MissingDateOfBirth(enc).failureNel[AdtError]) def hasAdmitDt: ValidationNel[AdtError, Timestamp] = enc.admitDT.map(_.successNel).getOrElse(MissingAdmitDate(enc).failureNel[AdtError]) def hasTimezone: ValidationNel[AdtError, DateTimeZone] = fac.timezone.map(_.successNel).getOrElse(UndefinedTimezone(fac).failureNel[AdtError]) // 现在|@|可以正常组合三个验证了 (hasDob |@| hasAdmitDt |@| hasTimezone) { (dob, admitTime, facilityTz) => // 这里写你的业务处理逻辑,比如构造验证后的对象 }
内容的提问来源于stack exchange,提问作者Sujit Baniya

