2

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

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

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

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

4

2 回答 2

3

对于快速失败的链式验证,它EitherValidated. 您可以轻松地从EitherValidated或反之,这取决于您是否想要累积错误。

解决您的问题的一个可能方法是创建一个智能构造函数,User该构造函数返回 anEither[Message, User]并将 this 与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)))
于 2016-12-18T09:52:47.753 回答
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)
于 2016-12-18T10:47:00.060 回答