使用 scala cat 检查非空字段的组合

Using scala cats to check combination of non-empty fields

在我的代码中我有一个 class 要检查它是否有效我应该评估是否存在至少一个可能的字段组合(存在我的意思是组合的每个字段不应该是空的)。示例:

case class Test( a: Option[String]
               , b: Option[String]
               , c: Option[String]
               , d: Option[String]
               , e: Option[Double]
               , f: Option[Double])

要“有效”,必须至少存在以下字段组合之一(“a,b,c”,”a,d,e”,”a,f”)

我试图用 scala cats 库来做这件事,但我有点迷路了。任何建议将不胜感激。

如果你想验证它你可以:

case class Test( a: Option[String]
               , b: Option[String]
               , c: Option[String]
               , d: Option[String]
               , e: Option[Double]
               , f: Option[Double]) {
  // "a,b,c","a,d,e","a,f"
  def isValid = (a.isDefined && b.isDefined && c.isDefined) ||
                (a.isDefined && d.isDefined && e.isDefined) ||
                (a.isDefined && f.isDefined)
}

如果你想确保只有定义了其中一个字段才能创建它,你将不得不使用智能构造函数

sealed abstract case class Test private ( a: Option[String]
                                        , b: Option[String]
                                        , c: Option[String]
                                        , d: Option[String]
                                        , e: Option[Double]
                                        , f: Option[Double])
object Test {

  def create( a: Option[String]
            , b: Option[String]
            , c: Option[String]
            , d: Option[String]
            , e: Option[Double]
            , f: Option[Double]): Either[String, Test] =
  if ((a.isDefined && b.isDefined && c.isDefined) ||
      (a.isDefined && d.isDefined && e.isDefined) ||
      (a.isDefined && f.isDefined))
    Right(new Test(a, b, c, d, e, f) {})
  else
    Left("All arguments are empty")
}

或者使用 ADT 确保定义了其中一个字段:

sealed trait Test extends Product with Serializable
object Test {
  final case class Case1( a: String
                        , b: String
                        , c: String
                        , d: Option[String]
                        , e: Option[Double]
                        , f: Option[Double]) extends Test
  final case class Case2( a: String
                        , b: Option[String]
                        , c: Option[String]
                        , d: String
                        , e: String
                        , f: Option[Double]) extends Test
  final case class Case3( a: String
                        , b: Option[String]
                        , c: Option[String]
                        , d: Option[String]
                        , e: Option[Double]
                        , f: Double) extends Test
}

您可以在此处使用猫...但是为了什么?您没有将元组或选项集合组合成单个选项。您不会将 F[Option[X] 换成 Option[F[X] 或相反。没有副作用、映射、遍历、从嵌入在某些上下文中的较小对象构建新对象等。您可以尝试做一些事情,比如

implicit val booleanMonoid: Monoid[Boolean] = new Monoid[Boolean] {
  def combine(a: Boolean, b: Boolean) = a && b
  def empty = true
}
def isValid = List(a, b, c).foldMap(_.isDefined) ||
              List(a, d, e).foldMap(_.isDefined) ||
              List(a, f).foldMap(_.isDefined)

甚至可能

def isValid = (
  (a, b, c).tupled.void orElse (a, d, e).tupled.void orElse (a, f).tupled.void
).isDefined

但这并不比

好多少
def isValid = List(a, b, c).exists(_.isDefined) ||
              List(a, d, e).exists(_.isDefined) ||
              List(a, f).exists(_.isDefined)

可在普通 Scala 中实现。我想你可以简单地通过使用 Option[_] 上定义的一些 Ring 来使用 *+:

implicit val ring: Ring[Option[_]] = ... // yup, existential type here
def isValid = ((a * b * c) + (a * d * e) + (a * f)).isDefined

(这需要 typelevel algebra)但是如果只在一个地方使用它,我看不到增益。

您可以使用 cats *> 运算符组合您感兴趣的字段并测试结果是否仍为非空。 a *> b 是 shorthand 对应 a.flatMap(_ => b)

import cats.implicits._

def isValid(t: Test) = 
  (t.a *> t.b *> t.c).nonEmpty ||
  (t.a *> t.d *> t.e).nonEmpty ||
  (t.a *> t.f).nonEmpty

但无论哪种方式,您都可以在没有猫的情况下实现与 List(...).forall(_.nonEmpty) 几乎相似的简洁性。