Использование scala cats для проверки комбинации непустых полей

#scala #scala-cats

#scala #scala-cats

Вопрос:

В моем коде у меня есть класс, который, чтобы проверить, действителен ли он, я должен оценить, существует ли хотя бы одна из возможных комбинаций полей (под exist я подразумеваю, что каждое поле комбинации не должно быть пустым). Пример:

 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, но я немного заблудился. Любое предложение будет высоко оценено.

Комментарии:

1. Я бы просто написал простое соответствие шаблону, поскольку набор проверяемых комбинаций статичен и мал.

2.итак, вы хотите проверить, что хотя бы одно из option isDefined ?

Ответ №1:

Если вы хотите проверить это, вы можете просто:

 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 amp;amp; b.isDefined amp;amp; c.isDefined) ||
                (a.isDefined amp;amp; d.isDefined amp;amp; e.isDefined) ||
                (a.isDefined amp;amp; 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 amp;amp; b.isDefined amp;amp; c.isDefined) ||
      (a.isDefined amp;amp; d.isDefined amp;amp; e.isDefined) ||
      (a.isDefined amp;amp; 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
}
 

Вы могли бы использовать Cats здесь… но для чего? Вы не объединяете кортеж или набор опций в одну опцию. Вы не меняете F[Option[X] местами Option[F[X] или наоборот. Нет никаких побочных эффектов, сопоставлений, обходов, конструкций новых объектов из меньших объектов, встроенных в некоторый контекст, И т. Д. Вы можете попробовать делать такие вещи, как

 implicit val booleanMonoid: Monoid[Boolean] = new Monoid[Boolean] {
  def combine(a: Boolean, b: Boolean) = a amp;amp; 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. Я думаю, вы могли бы просто использовать обозначения, используя некоторые Ring определенные Option[_] для использования * и :

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

(для чего потребуется алгебра на уровне типов), но для ее использования только в одном месте я бы не увидел выигрыша.

Комментарии:

1. Обратите внимание, что OP не хочет, чтобы был определен хотя бы один или не более одного определенного, он / она хочет несколько комбинаций: ("a,b,c","a,d,e","a,f") но, как я уже сказал (и вы подтверждаете), это вряд ли имеет какое-либо отношение к cats , просто напишите проверки вручную, вот и все.

2. Только что заметил, исправляя это.

Ответ №2:

Вы можете использовать оператор cats *> для объединения интересующих вас полей и проверки, остается ли результат непустым. a *> b является сокращением для 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) помощью without cats.

Комментарии:

1.Это не сработает, если OP хочет, a b c чтобы для первого случая был определен только amp; .

2. Но он не сказал только. Он сказал хотя бы одну из этих комбинаций. Если это именно одна из тех комбинаций, которые должны быть определены, он должен обязательно проанализировать их в соответствующий ADT как можно скорее.

3. Да, именно поэтому я сказал «если» , из вопроса действительно неясно, чего именно хочет OP. Но просто хотел отметить это.