在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可能是相关的,不确定。
答案 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)