在 Scala Cats Validated 中,如何组合有序验证
In Scala Cats Validated, how can combine ordered validations
在 https://gist.github.com/satyagraha/897e427bfb5ed203e9d3054ac6705704 我发布了一个 Scala Cats 验证场景,看起来很合理,但我还没有找到一个非常好的解决方案。
本质上,有一个两阶段验证,验证各个字段,然后调用 class 构造函数,这可能会由于内部检查而抛出(通常这可能不受我的控制来更改,因此异常处理代码)。我们希望在任何字段验证失败时不调用构造函数,但也希望将任何构造函数失败合并到最终结果中。 "Fail-fast"二相检查就在这里
这是一种 flatMap
问题,cats.data.Validated
框架似乎可以通过 cats.data.Validated#andThen
操作来处理。但是,正如您在代码中看到的那样,我找不到解决该问题的特别巧妙的方法。 cats.syntax.CartesianBuilder
上可用的操作数量非常有限,我不清楚如何使用 andThen
操作 link 它。
欢迎提出任何想法!注意有一个 Cats 问题 https://github.com/typelevel/cats/issues/1343 可能是相关的,不确定。
对于快速失败、链式验证,使用 Either
比 Validated
更容易。您可以轻松地从 Either
切换到 Validated
,反之亦然,具体取决于您是否需要累积误差。
您的问题的一个可能解决方案是为 User
创建一个智能构造函数,其中 returns 和 Either[Message, User]
并将其与 Validated[Message, (Name, Date)]
.[=18= 一起使用]
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)))
我会创建一个辅助二阶函数来包装抛出异常的函数:
def attempt[A, B](f: A => B): A => Validated[Message, B] = a => tryNonFatal(f(a))
另外,case 类 的默认 companion 扩展了 FunctionN trait,所以不需要做 (User.apply _).tupled
,它可以缩短为 User.tupled
(在自定义 companion 上,你需要写入 extends ((...) => ...))
但 apply
覆盖将自动生成)
所以我们最终使用 andThen
:
val valids = validateName(nameRepr) |@| validateDate(dateDepr)
val res: Validated[Message, User] = valids.tupled andThen attempt(User.tupled)
在 https://gist.github.com/satyagraha/897e427bfb5ed203e9d3054ac6705704 我发布了一个 Scala Cats 验证场景,看起来很合理,但我还没有找到一个非常好的解决方案。
本质上,有一个两阶段验证,验证各个字段,然后调用 class 构造函数,这可能会由于内部检查而抛出(通常这可能不受我的控制来更改,因此异常处理代码)。我们希望在任何字段验证失败时不调用构造函数,但也希望将任何构造函数失败合并到最终结果中。 "Fail-fast"二相检查就在这里
这是一种 flatMap
问题,cats.data.Validated
框架似乎可以通过 cats.data.Validated#andThen
操作来处理。但是,正如您在代码中看到的那样,我找不到解决该问题的特别巧妙的方法。 cats.syntax.CartesianBuilder
上可用的操作数量非常有限,我不清楚如何使用 andThen
操作 link 它。
欢迎提出任何想法!注意有一个 Cats 问题 https://github.com/typelevel/cats/issues/1343 可能是相关的,不确定。
对于快速失败、链式验证,使用 Either
比 Validated
更容易。您可以轻松地从 Either
切换到 Validated
,反之亦然,具体取决于您是否需要累积误差。
您的问题的一个可能解决方案是为 User
创建一个智能构造函数,其中 returns 和 Either[Message, User]
并将其与 Validated[Message, (Name, Date)]
.[=18= 一起使用]
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)))
我会创建一个辅助二阶函数来包装抛出异常的函数:
def attempt[A, B](f: A => B): A => Validated[Message, B] = a => tryNonFatal(f(a))
另外,case 类 的默认 companion 扩展了 FunctionN trait,所以不需要做 (User.apply _).tupled
,它可以缩短为 User.tupled
(在自定义 companion 上,你需要写入 extends ((...) => ...))
但 apply
覆盖将自动生成)
所以我们最终使用 andThen
:
val valids = validateName(nameRepr) |@| validateDate(dateDepr)
val res: Validated[Message, User] = valids.tupled andThen attempt(User.tupled)