在Scala Cats Validated中,如何组合有序验证

时间:2016-12-18 00:07:49

标签: scala validation scala-cats

https://gist.github.com/satyagraha/897e427bfb5ed203e9d3054ac6705704我发布了一个看似合理的Scala Cats验证方案,但我还没有找到一个非常简洁的解决方案。

基本上,有一个两阶段验证,其中验证了各个字段,然后调用了一个类构造函数,它可能由于内部检查而抛出(通常这可能不在我的控制下进行更改,因此异常处理代码)。如果任何字段验证失败,我们希望不调用构造函数,而是将任何构造函数失败组合到最终结果中。 "快速失败"绝对是在这里进行两阶段检查。

这是一种flatMap问题,cats.data.Validated框架似乎通过cats.data.Validated#andThen操作处理。但是,正如您在代码中看到的那样,我无法找到解决问题的特别巧妙的解决方案。 cats.syntax.CartesianBuilder上提供的操作数量非常有限,我不清楚如何将其与andThen操作相关联。

欢迎任何想法!请注意,有一个猫问题https://github.com/typelevel/cats/issues/1343可能是相关的,不确定。

2 个答案:

答案 0 :(得分:3)

对于快速失败的链式验证,使用Either比使用Validated更容易。您可以轻松地从Either切换到Validated,反之亦然,具体取决于您是否需要错误累积。

问题的一个可能解决方案是为User创建一个智能构造函数,该构造函数返回Either[Message, User]并将其与Validated[Message, (Name, Date)]一起使用。

import cats.implicits._
import cats.data.Validated

def user(name: Name, date: Date): Either[Message, User] = 
  Either.catchNonFatal(User(name, date)).leftMap(Message.toMessage)

// error accumulation -> Validated
val valids: Validated[Message, (Name, Date)] = 
  (validateName(nameRepr) |@| validateDate(dateDepr)).tupled

// error short circuiting -> either
val userOrMessage: Either[Message, User] =
  valids.toEither.flatMap((user _).tupled)

// Either[Message,User] = Right(User(Name(joe),Date(now)))

答案 1 :(得分:1)

我会创建一个辅助二阶函数来包装抛出异常的函数:

def attempt[A, B](f: A => B): A => Validated[Message, B] = a => tryNonFatal(f(a))

此外,案例类的默认伴侣扩展了FunctionN特征,因此不需要(User.apply _).tupled,它可以缩短为User.tupled(在自定义伴侣上,您需要编写extends ((...) => ...)) apply覆盖将自动生成)

因此我们最终使用andThen

val valids = validateName(nameRepr) |@| validateDate(dateDepr)
val res: Validated[Message, User] = valids.tupled andThen attempt(User.tupled)