简体   繁体   中英

Scalaz Validation, validate inner value

I have a Validation object

val v = Validation[String, Option[Int]]

I need to make a second validation, to check if actual Integer value is equals to 100 for example. If I do

val vv = v.map(_.map(intValue => if (intValue == 100) 
                               intValue.success[String] 
                           else 
                               "Bad value found".fail[Integer]))

I get:

Validation[String, Option[Validation[String, Int]]]

How is it possible to get vv also as Validation[String, Option[Int]] in most concise way

=========

Found possible solution from my own:

val validation: Validation[String, Option[Int]] = Some(100).success[String]

val validatedTwice: Validation[String, Option[Int]] = validation.fold(
  _ => validation,                             // if Failure then return it
  _.map(validateValue _) getOrElse validation  // validate Successful result
)

def validateValue(value: Int): Validation[String, Option[Int]] = {
  if (value == 100)
    Some(value).success[String]
  else
    "Bad value".fail[Option[Int]]
}

Looks not concise and elegant although it works

==============

Second solution from my own, but also looks over-compicated:

val validatedTwice2: Validation[String, Option[Int]] = validation.flatMap(
    _.map(validateValue _).map(_.map(Some(_))) getOrElse validation)

def validateValue(value: Int): Validation[String, Int] = {
    if (value == 100)
      value.success[String]
    else
      "Bad value".fail[Int]
}

First, let's set up some type aliases because typing this out repeatedly will get old pretty fast. We'll tidy up your validation logic a little too while we're here.

type V[X] = Validation[String, X]
type O[X] = Option[X]
def checkInt(i: Int): V[Int] = Validation.fromEither(i != 100 either "Bad value found" or i)

val v: V[O[Int]] = _

this is where we're starting out - b1 is equivalent to your vv situation

val b1: V[O[V[Int]]] = v.map(_.map(checkInt))

so let's sequence the option to flip over the V[O[V[Int]]] into a V[V[O[Int]]]

val b2: V[V[O[Int]]] = v.map(_.map(checkInt)).map(_.sequence[V, Int])

or if you're feeling lambda-y it could have been

sequence[({type l[x] = Validation[String, x]})#l, Int]

next we flatten out that nested validation - we're going to pull in the Validation monad because we actually do want the fastfail behaviour here, although it's generally not the right thing to do.

implicit val monad = Validation.validationMonad[String]
val b3: V[O[Int]] = v.map(_.map(checkInt)).map(_.sequence[V, Int]).join

So now we've got a Validation[String, Option[Int]], so we're there, but this is still pretty messy. Lets use some equational reasoning to tidy it up

By the second functor law we know that:

X.map(_.f).map(_.g) = X.map(_.f.g) =>
    val i1: V[O[Int]] = v.map(_.map(checkInt).sequence[V, Int]).join

and by the definition of a monad:

X.map(f).join = X.flatMap(f) =>
    val i2: V[O[Int]] = v.flatMap(_.map(checkInt).sequence[V, Int])

and then we apply the free theorem of traversal:
(I struggled with that bloody paper so much, but it looks like some of it sunk in!):

X.map(f).sequence = X.traverse(f andThen identity) = X.traverse(f) =>
    val i3: V[O[Int]] = v.flatMap(_.traverse[V, Int](checkInt))

so now we're looking at something a bit more civilised. I imagine there's some trickery to be played with the flatMap and traverse, but I've run out of inspiration.

Your solution is over-complicated. The following will suffice!

v flatMap (_.filter(_ == 100).toSuccess("Bad value found"))

The toSuccess comes from OptionW and converts an Option[A] into a Validation[X, A] taking the value provided for the failure case in the event that the option is empty. The flatMap works like this:

Validation[X, A] 
          => (A => Validation[X, B]) 
                                => (via flatMap) Validation[X, B]

That is, flatMap maps and then flattens ( join in scalaz-parlance):

Validation[X, A]
          => (A => Validation[X, B]]
                            => (via map) Validation[X, Validation[X, B]]
                                                  =>  (via join) Validation[X, B]

使用flatMap ,如下所示:

v.flatMap(_.parseInt.fail.map(_.getMessage).validation)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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