简体   繁体   English

如何在Scala中的猫中组合具有实用效果的功能以进行验证

[英]How to compose functions with applicative effects for Validation in the Cats in Scala

Here is an example from the Scala with Cats book : 这是Scala with Cats书中的示例:

object Ex {

  import cats.data.Validated

  type FormData = Map[String, String]
  type FailFast[A] = Either[List[String], A]

  def getValue(name: String)(data: FormData): FailFast[String] =
    data.get(name).toRight(List(s"$name field not specified"))
  type NumFmtExn = NumberFormatException

  import cats.syntax.either._ // for catchOnly
  def parseInt(name: String)(data: String): FailFast[Int] =
    Either.catchOnly[NumFmtExn](data.toInt).leftMap(_ => List(s"$name must be an integer"))

  def nonBlank(name: String)(data: String): FailFast[String] =
    Right(data).ensure(List(s"$name cannot be blank"))(_.nonEmpty)

  def nonNegative(name: String)(data: Int): FailFast[Int] =
    Right(data).ensure(List(s"$name must be non-negative"))(_ >= 0)


  def readName(data: FormData): FailFast[String] =
    getValue("name")(data).
      flatMap(nonBlank("name"))

  def readAge(data: FormData): FailFast[Int] =
    getValue("age")(data).
      flatMap(nonBlank("age")).
      flatMap(parseInt("age")).
      flatMap(nonNegative("age"))

  case class User(name: String, age: Int)

  type FailSlow[A] = Validated[List[String], A]
  import cats.instances.list._ // for Semigroupal
  import cats.syntax.apply._ // for mapN
  def readUser(data: FormData): FailSlow[User] =
    (
      readName(data).toValidated,
      readAge(data).toValidated
    ).mapN(User.apply)

Some notes: each primitive validation function: nonBlank , nonNegative , getValue returns so called FailFast type, which is monadic, not applicative. 一些注意事项:每个原始验证函数: nonBlanknonNegativegetValue返回所谓的FailFast类型,它是单子函数,不适用。

There are 2 functions readName and readAge , which use a composition of the previous ones, and also are FailFast by the nature. 有两个函数readNamereadAge ,它们使用以前的函数组成,并且本质上也是FailFast。

The readUser is on the contrary, fail slow. 相反, readUser失败较慢。 To achieve it results of readName and readAge are converted to Validated and composed through so called "Syntax" 为此,将readNamereadAge结果转换为Validated并通过所谓的“语法”组成

Let's assume I have another function for validation, that accepts name and age, validated by readName and readAge . 假设我还有另一个用于验证的函数,该函数接受名称和年龄,并由readNamereadAge验证。 For intstance: 对于实例:

  //fake implementation:
  def validBoth(name:String, age:Int):FailSlow[User] =
    Validated.valid[List[String], User](User(name,age))

How to compose validBoth with readName and readAge? 如何撰写validBothreadName和readAge? With fail fast it is quite simple, cause I use for-comrehension and have access to the results of readName and readAge : 快速失败非常简单,因为我使用for-comrehension readName并且可以访问readNamereadAge的结果:

for {
  n <- readName...
  i <-  readAge...
  t <- validBoth(n,i)
} yield t

but how to get the same result for failslow? 但是如何为failslow获得相同的结果?

EDIT probably it is not clear enough, with these function. 用这些功能编辑可能还不够清楚。 Here is a real use case. 这是一个真实的用例。 There is a function, similar to readName/readAge that validates date in the similar way. 有一个类似于readName / readAge的函数,它以类似的方式验证日期。 I want to create a validation fucntion, that accepts 2 dates, to make sure that one date comes after another. 我想创建一个接受两个日期的验证功能,以确保一个日期紧随另一个日期。 Date comes from String. 日期来自字符串。 Here is an example, how it will look like for FailFast, which is not the best option in this context: 这是一个示例,显示FailFast的样子,在这种情况下这不是最佳选择:

def oneAfterAnother(dateBefore:Date, dateAfter:Date): FailFast[Tuple2[Date,Date]] = 
  Right((dateBefore, dateAfter))
    .ensure(List(s"$dateAfter date cannot be before $dateBefore"))(t => t._1.before(t._2))

for {
  dateBefore <- readDate...
  dateAfter <-  readDate...
  t <- oneDateAfterAnother(dateBefore,dateAfter)
} yield t

My purpose is to accumulate possible errors with dates in applicative way. 我的目的是以适用方式累积日期可能出现的错误。 In the book it is said, p. 在书中说,p。 157: 157:

We can't flatMap because Validated isn't a monad. 我们无法进行FlatMap,因为Validated不是monad。 However, Cats does provide a stand-in for flatMap called andThen . 但是,Cats确实提供了名为andThen的flatMap替代品。 The type signature of andThen is identical to that of flatMap, but it has a different name because it is not a lawful implementation with respect to the monad laws: andThen的类型签名与flatMap的类型签名相同,但是其名称有所不同,因为它不是就monad法则而言的合法实现:

32.valid.andThen { a =>
  10.valid.map { b =>
    a + b
  }
}

Ok, I tried to reuse this solution, based on andThen , but the result had monadic, but not applicative effect: 好的,我尝试基于andThen重用此解决方案,但结果产生了andThen的效果,但没有应用效果:

  def oneDateAfterAnotherFailSlow(dateBefore:String, dateAfter:String)
                                 (map: Map[String, String])(format: SimpleDateFormat)
  : FailSlow[Tuple2[Date, Date]] =
    readDate(dateBefore)(map)(format).toValidated.andThen { before =>
      readDate(dateAfter)(map)(format).toValidated.andThen { after =>
        oneAfterAnother(before,after).toValidated
      }
    }

Maybe the code is self-explanatory here: 也许代码在这里是不言自明的:

/** Edited for the new question. */
import cats.data.Validated
import cats.instances.list._ // for Semigroup
import cats.syntax.apply._ // for tupled
import cats.syntax.either._ // for toValidated

type FailFast[A] = Either[List[String], A]
type FailSlow[A] = Validated[List[String], A]
type Date = ???
type SimpleDateFormat = ???

def readDate(date: String)
            (map: Map[String, String])
            (format: SimpleDateFormat): FailFast[Date] = ???

def oneDateAfterAnotherFailSlow(dateBefore: String, dateAfter: String)
                       (map: Map[String, String])
                       (format: SimpleDateFormat): FailSlow[(Date, Date)] =
  (
    readDate(dateBefore)(map)(format).toValidated,
    readDate(dateAfter)(map)(format).toValidated
  ).tupled.ensure(List(s"$dateAfter date cannot be before $dateBefore"))(t => t._1.before(t._2))

The thing with Applicatives is that you should not (and if working with the abastraction can not) use flatMap since that will have sequential semantics (In this case FailFast behavior) . 带有Applicatives的事情是,您不应该(如果不能使用抽象技术,则不能)使用flatMap因为它将具有顺序语义(在这种情况下为FailFast行为)
Thus, you need to use the abstractions that they provide, usually mapN to call a function with all the arguments if all of they are valid or tupled to create a tuple. 因此,你需要使用抽象,他们提供的,通常mapN调用一个函数的所有参数,如果所有这些都是有效的或tupled创建一个元组。

Edit 编辑

As the documentation states andThen should be used where you want your Validated to work as a Monad without being one. 如文档所述, andThen在您希望Validated作为Monad而不是Monad的地方使用。
It is there just by convenience, but you should not used it if you want the FailSlow semantics. 它只是为了方便而存在,但是如果您需要FailSlow语义,则不要使用它。

"This function is similar to flatMap on Either. It's not called flatMap, because by Cats convention, flatMap is a monadic bind that is consistent with ap. This method is not consistent with ap (or other Apply-based methods), because it has "fail-fast" behavior as opposed to accumulating validation failures". “此函数类似于Either上的flatMap。它不称为flatMap,因为按照Cats约定,flatMap是与ap一致的单子绑定。此方法与ap(或其他基于Apply的方法)不一致,因为它具有与累积验证失败相反的“快速失败”行为”。

I could compose it finally with the following code: 我最终可以用以下代码编写它:

  import cats.syntax.either._
  import cats.instances.list._ // for Semigroupal
  def oneDateAfterAnotherFailSlow(dateBefore:String, dateAfter:String)
                                 (map: Map[String, String])(format: SimpleDateFormat)
  : FailFast[Tuple2[Date, Date]] =
    for {
      t <-Semigroupal[FailSlow].product(
          readDate(dateBefore)(map)(format).toValidated,
          readDate(dateAfter)(map)(format).toValidated
        ).toEither
      r <- oneAfterAnother(t._1, t._2)
    } yield r

The idea, is that first validations for strings are applied, to make sure dates are correct. 这个想法是,首先对字符串进行验证,以确保日期正确。 They are accumulated with Validated(FailSlow). 它们与Validated(FailSlow)一起累积。 Then fail-fast is used, cause if any of the dates is wrong and cannot be parsed, it makes no sense to continue and to compare them as dates. 然后使用快速失败,因为如果任何日期错误并且无法解析,则继续并将它们作为日期进行比较是没有意义的。

It passed through my test cases. 它通过了我的测试用例。

If you can offer another, more elegant solution, always welcome! 如果您可以提供其他更优雅的解决方案,请随时欢迎!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM