我有一种情况,我有几个案例类,其中所有变量都是可选的。
让我说我有:
case class Size(width: Option[Int], height: Option[Int])
case class Foo(a: Option[String], b: Option[Boolean], c: Option[Char])
给定一个相同类型的案例类的集合,我想将它们折叠起来,比较选项值并保留定义的值。即Size
:
values.foldLeft(x) { (a, b) =>
Size(a.width.orElse(b.width), a.height.orElse(b.height))
}
我想以更一般的方式为上述任何案例类做这件事。我正在考虑用unapply(_).get
等做一些事情。有没有人知道解决这个问题的聪明方法?
答案 0 :(得分:2)
好的,请考虑一下:
def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
(coll: Seq[C]): C = {
coll.tail.foldLeft(coll.head) { case (current, next) =>
apply(unapply(current).get orElse unapply(next).get)
}
}
case class Person(name: Option[String])
foldCase(Person.unapply, Person.apply)(List(Person(None), Person(Some("Joe")), Person(Some("Mary"))))
可以重载foldCase
以接受两个,三个或更多参数,每个arity的一个版本f。然后它可以用于任何案例类。由于需要担心元组问题,因此下面是使用case类或两个参数的一种方法。将它扩展到更多参数是微不足道的,虽然有点令人厌烦。
def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
(coll: Seq[C]): C = {
def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
apply(current._1 orElse next._1, current._2 orElse next._2)
coll.tail.foldLeft(coll.head) { case (current, next) =>
thisOrElse(unapply(current).get, unapply(next).get)
}
}
val list = Person(None, None) :: Person(Some("Joe"), None) :: Person(None, Some(20)) :: Person(Some("Mary"), Some(25)) :: Nil
def foldPerson = foldCase(Person.unapply, Person.apply) _
foldPerson(list)
要使用它重载,只需将所有定义放在一个对象中:
object Folder {
def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
(coll: Seq[C]): C = {
coll.tail.foldLeft(coll.head) { case (current, next) =>
apply(unapply(current).get orElse unapply(next).get)
}
}
def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
(coll: Seq[C]): C = {
def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
apply(current._1 orElse next._1, current._2 orElse next._2)
coll.tail.foldLeft(coll.head) { case (current, next) =>
thisOrElse(unapply(current).get, unapply(next).get)
}
}
}
但是,当您执行此操作时,您必须明确地将apply
和unapply
转换为函数:
case class Question(answer: Option[Boolean])
val list2 = List(Question(None), Question(Some(true)), Question(Some(false)))
Folder.foldCase(Question.unapply _, Question.apply _)(list2)
有可能将其转换为结构类型,因此您只需要传递伴随对象,但我无法做到。在#scala上,我被告知答案是肯定的,至少我是如何处理这个问题的。
答案 1 :(得分:2)
[代码更新]
这是一个解决方案,每个“arity”只需要一个抽象类:
abstract class Foldable2[A,B](val a:Option[A], val b:Option[B]) {
def orElse[F <: Foldable2[A,B]](that: F)(implicit ev: this.type <:< F) =
getClass.getConstructor(classOf[Option[A]], classOf[Option[B]]).newInstance(
this.a.orElse(that.a), this.b.orElse(that.b)
)
}
case class Size(w: Option[Int], h: Option[Int]) extends Foldable2(w, h)
println(Size(Some(1),None).orElse(Size(Some(2),Some(42))))
//--> Size(Some(1),Some(42))
请注意,当具有相同构造函数参数的其他案例类传递给方法时,隐式<:<
参数将给出编译时错误。
但是,需要一个“格式良好”的构造函数,否则反射代码会爆炸。
答案 2 :(得分:1)
您可以使用productElement
或productIterator
(在scala.Product上)来一般检索/迭代案例类(和元组)的元素,但它们的输入为Any,所以会有些痛苦。