7

我正在尝试编写一些代码来轻松链接返回 ScalazValidation类型的函数。我正在尝试编写的一种方法类似于Validation.flatMap我将调用的 (Short circuit that validation) andPipe。另一个类似于|@|on ApplicativeBuilder(累积错误),除了它只返回最终Success类型,我将调用它andPass

假设我有功能:

def allDigits: (String) => ValidationNEL[String, String]
def maxSizeOfTen: (String) => ValidationNEL[String, String] 
def toInt: (String) => ValidationNEL[String, Int]

例如,我想首先将输入字符串传递给 allDigits 和 maxSizeOf10。如果有故障,它应该通过不调用 toInt 函数来短路并返回发生的一个或两个故障。如果成功,我想将 Success 值传递给 toInt 函数。从那里开始,它要么成功,输出值为 Int,要么失败,仅返回来自 toInt 的验证失败。

def intInput: (String) => ValidationNEL[String,Int] = (allDigits andPass maxSizeOfTen) andPipe toInt 

如果没有下面的附加实现,有没有办法做到这一点?

这是我的实现:

  trait ValidationFuncPimp[E,A,B] {
    val f: (A) => Validation[E, B]

    /** If this validation passes, pass to f2, otherwise fail without accumulating. */
    def andPipe[C](f2: (B) => Validation[E,C]): (A) => Validation[E,C] = (a: A) => {
      f(a) match {
        case Success(x) => f2(x)
        case Failure(x) => Failure(x)
      }
    }

    /** Run this validation and the other validation, Success only if both are successful.  Fail accumulating errors. */
    def andPass[D](f2: (A) => Validation[E,D])(implicit S: Semigroup[E]): (A) => Validation[E,D] = (a:A) => {
      (f(a), f2(a)) match {
        case (Success(x), Success(y)) => Success(y)
        case (Failure(x), Success(y)) => Failure(x)
        case (Success(x), Failure(y)) => Failure(y)
        case (Failure(x), Failure(y)) => Failure(S.append(x, y))
      }
    }
  }
  implicit def toValidationFuncPimp[E,A,B](valFunc : (A) => Validation[E,B]): ValidationFuncPimp[E,A,B] = {
    new ValidationFuncPimp[E,A,B] {
      val f = valFunc
    }
  }
4

3 回答 3

5

我并不是说这个答案一定比drstevens 的更好,但它采用了稍微不同的方法,不适合在那里发表评论。

首先是我们的验证方法(注意我已经改变了toInt一点的类型,原因我将在下面解释):

import scalaz._, Scalaz._

def allDigits: (String) => ValidationNEL[String, String] =
  s => if (s.forall(_.isDigit)) s.successNel else "Not all digits".failNel

def maxSizeOfTen: (String) => ValidationNEL[String, String] =
  s => if (s.size <= 10) s.successNel else "Too big".failNel

def toInt(s: String) = try(s.toInt.right) catch {
  case _: NumberFormatException => NonEmptyList("Still not an integer").left
}

为了方便起见,我将定义一个类型别名:

type ErrorsOr[+A] = NonEmptyList[String] \/ A

现在我们刚刚得到了几个 Kleisli 箭头:

val validator = Kleisli[ErrorsOr, String, String](
  allDigits.flatMap(x => maxSizeOfTen.map(x *> _)) andThen (_.disjunction)
)

val integerizer = Kleisli[ErrorsOr, String, Int](toInt)

我们可以组成:

val together = validator >>> integerizer

并像这样使用:

scala> together("aaa")
res0: ErrorsOr[Int] = -\/(NonEmptyList(Not all digits))

scala> together("12345678900")
res1: ErrorsOr[Int] = -\/(NonEmptyList(Too big))

scala> together("12345678900a")
res2: ErrorsOr[Int] = -\/(NonEmptyList(Not all digits, Too big))

scala> together("123456789")
res3: ErrorsOr[Int] = \/-(123456789)

使用非 monadicflatMap的东西让我有点不舒服,将我们的两种ValidationNEL方法组合成\/monad 中的 Kleisli 箭头——这也是我们字符串到整数转换的合适模型——对我来说感觉更干净一些。

于 2012-12-24T01:02:08.237 回答
3

这相对简洁,几乎没有“添加代码”。虽然它仍然有点不稳定,因为它忽略了应用的成功结果allDigits

scala> val validated = for {
     |   x <- allDigits
     |   y <- maxSizeOfTen
     | } yield x *> y
validated: String => scalaz.Validation[scalaz.NonEmptyList[String],String] = <function1>

scala> val validatedToInt = (str: String) => validated(str) flatMap(toInt)
validatedToInt: String => scalaz.Validation[scalaz.NonEmptyList[String],Int] = <function1>

scala> validatedToInt("10")
res25: scalaz.Validation[scalaz.NonEmptyList[String],Int] = Success(10)

或者,您可以保留 和 的两个allDigits输出maxSizeOfTen

val validated2 = for {
  x <- allDigits
  y <- maxSizeOfTen
} yield x <|*|> y

我很好奇其他人是否能想出一个更好的方法来结合这些。真的不是合成...

val validatedToInt = (str: String) => validated2(str) flatMap(_ => toInt(str))

validated和累积validated2失败如下所示:

scala> def allDigits: (String) => ValidationNEL[String, String] = _ => failure(NonEmptyList("All Digits Fail"))
allDigits: String => scalaz.Scalaz.ValidationNEL[String,String]

scala> def maxSizeOfTen: (String) => ValidationNEL[String, String] = _ => failure(NonEmptyList("max > 10"))
maxSizeOfTen: String => scalaz.Scalaz.ValidationNEL[String,String]

scala> val validated = for {
     |   x <- allDigits
     |   y <- maxSizeOfTen
     | } yield x *> y
validated: String => scalaz.Validation[scalaz.NonEmptyList[String],String] = <function1>

scala> val validated2 = for {
     |   x <- allDigits
     |   y <- maxSizeOfTen
     | } yield x <|*|> y
validated2: String => scalaz.Validation[scalaz.NonEmptyList[String],(String, String)] = <function1>

scala> validated("ten")
res1: scalaz.Validation[scalaz.NonEmptyList[String],String] = Failure(NonEmptyList(All Digits Fail, max > 10))

scala> validated2("ten")
res3: scalaz.Validation[scalaz.NonEmptyList[String],(String, String)] = Failure(NonEmptyList(All Digits Fail, max > 10))
于 2012-12-19T23:48:55.507 回答
1

将 ApplicativeBuilder 与前两个一起使用,这样错误就会累积,然后是 flatMap toInt,因此toInt只有在前两个成功时才会调用。

val validInt: String => ValidationNEL[String, Int] = 
  for {
    validStr <- (allDigits |@| maxSizeOfTen)((x,_) => x); 
    i <- toInt
  } yield(i)
于 2012-12-20T03:20:09.283 回答