4

我有一个验证对象

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

我需要进行第二次验证,例如检查实际整数值是否等于 100。如果我做

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

我得到:

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

如何以最简洁的方式将 vv 也作为 Validation[String, Option[Int]]

=========

从我自己找到可能的解决方案:

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]]
}

虽然有效,但看起来并不简洁优雅

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

我自己的第二个解决方案,但也看起来过于复杂:

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]
}
4

3 回答 3

2

首先,让我们设置一些类型别名,因为重复输入会很快变老。当我们在这里时,我们也会稍微整理一下您的验证逻辑。

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]] = _

这是我们开始的地方 - b1 相当于您的 vv 情况

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

因此,让我们对将 V[O[V[Int]]] 翻转为 V[V[O[Int]]] 的选项进行排序

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

或者如果你感觉 lambda-y 它可能是

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

接下来我们将嵌套验证展平——我们将引入 Validation monad,因为我们实际上确实想要这里的 fastfail 行为,尽管这通常不是正确的做法。

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

所以现在我们有了一个 Validation[String, Option[Int]],所以我们在那里,但这仍然很混乱。让我们使用一些等式推理来整理它

根据第二函子定律,我们知道:

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

并根据单子的定义:

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

然后我们应用遍历的自由定理:(
我为那张该死的纸挣扎了很多,但它看起来有些沉没了!):

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

所以现在我们正在寻找更文明的东西。我想有一些技巧可以使用 flatMap 和 traverse,但我已经没有灵感了。

于 2012-08-15T11:07:46.353 回答
2

您的解决方案过于复杂。以下就够了!

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

在选项为空的情况下,来自并将 a 转换为为失败案例提供toSuccessOptionWOption[A]。作品是这样Validation[X, A]的:flatMap

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

也就是说,flatMap映射然后变平(join在scalaz-parlance中):

Validation[X, A]
          => (A => Validation[X, B]]
                            => (via map) Validation[X, Validation[X, B]]
                                                  =>  (via join) Validation[X, B]
于 2012-02-06T19:52:29.540 回答
0

使用flatMap,像这样:

v.flatMap(_.parseInt.fail.map(_.getMessage).validation)
于 2012-02-06T19:21:40.293 回答