#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. Но просто хотел отметить это.