First, let me set up some type aliases because repeating this set repeatedly will become pretty fast. We will remove your verification logic a little while we are 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 start - b1 is equivalent to your vv situation
val b1: V[O[V[Int]]] = v.map(_.map(checkInt))
so move the option to flip V [O [V [Int]]] to V [V [O [Int]]]
val b2: V[V[O[Int]]] = v.map(_.map(checkInt)).map(_.sequence[V, Int])
or if you feel lambda, it could be
sequence[({type l[x] = Validation[String, x]})
Then we smooth out this nested check - we are going to attract the monad of check, because in fact we really want fastfail behavior, although this is usually not the case.
implicit val monad = Validation.validationMonad[String] val b3: V[O[Int]] = v.map(_.map(checkInt)).map(_.sequence[V, Int]).join
So now we have Validation [String, Option [Int]], so we are there, but it is still pretty dirty. Let's use some equatorial reasoning to tidy it up.
According to the second law of the functor, we know that:
X.map(_.f).map(_.g) = X.map(_.fg) => val i1: V[O[Int]] = v.map(_.map(checkInt).sequence[V, Int]).join
and by 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 apply the free traversal theorem:
(I struggled so much with this bloody paper, but it looks like part of it has sunk!):
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 are looking at something more civilized. I suppose there is some kind of deception there that needs to be played with a flat mast and traverse, but my inspiration will run out.