链接Scalaz验证函数:Function1 [A,验证[E,B]]

Tra*_*ens 7 scala scalaz

我正在尝试编写一些代码,以便轻松链接返回Scalaz Validation类型的函数.我试图编写的一种方法类似于Validation.flatMap(我要调用的短路验证)andPipe.另一个类似于|@|on ApplicativeBuilder(累积错误),除了它只返回Success我将调用的最终类型andPass

假设我有功能:

def allDigits: (String) => ValidationNEL[String, String]
def maxSizeOfTen: (String) => ValidationNEL[String, String] 
def toInt: (String) => ValidationNEL[String, Int]
Run Code Online (Sandbox Code Playgroud)

作为一个例子,我想首先将输入String传递给allDigits和maxSizeOf10.如果存在故障,则应通过不调用toInt函数来短路,并返回发生的一个或两个故障.如果成功,我想将Success值传递给toInt函数.从那里,它将成功输出值为Int,或者它将无法仅从toInt返回验证失败.

def intInput: (String) => ValidationNEL[String,Int] = (allDigits andPass maxSizeOfTen) andPipe toInt 
Run Code Online (Sandbox Code Playgroud)

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

这是我的实施:

  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
    }
  }
Run Code Online (Sandbox Code Playgroud)

Tra*_*own 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
}
Run Code Online (Sandbox Code Playgroud)

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

type ErrorsOr[+A] = NonEmptyList[String] \/ A
Run Code Online (Sandbox Code Playgroud)

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

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

val integerizer = Kleisli[ErrorsOr, String, Int](toInt)
Run Code Online (Sandbox Code Playgroud)

我们可以撰写:

val together = validator >>> integerizer
Run Code Online (Sandbox Code Playgroud)

并使用这样的:

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)
Run Code Online (Sandbox Code Playgroud)

使用flatMap非monadic的东西让我有点不舒服,并将我们的两种ValidationNEL方法结合到\/monad中的Kleisli箭头中- 这也是我们的字符串到整数转换的合适模型 - 对我来说感觉有点清洁.