7

ScalazValidation型を返す関数を簡単にチェーンできるようにするためのコードを書こうとしています。私が書き込もうとしているメソッドの1つは、これからValidation.flatMap呼び出す(検証の短絡)に類似していますandPipe|@|もう1つは、 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

これで、クライスリの矢が2つできました。

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)

flatMapモナドではないものを使用すると、少し不快になります。2つのValidationNELメソッドをモナドのクライスリ矢印に組み合わせると\/、文字列から整数への変換の適切なモデルとしても機能しますが、少しすっきりします。

于 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

最初の2つでApplicativeBuilderを使用して、エラーが累積し、次にflatMapを使用してtoInttoInt最初の2つが成功した場合にのみ呼び出されるようにします。

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