折叠案例类

时间:2010-11-25 23:09:09

标签: scala

我有一种情况,我有几个案例类,其中所有变量都是可选的。

让我说我有:

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等做一些事情。有没有人知道解决这个问题的聪明方法?

3 个答案:

答案 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)
    }
  }
}

但是,当您执行此操作时,您必须明确地将applyunapply转换为函数:

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)

您可以使用productElementproductIterator(在scala.Product上)来一般检索/迭代案例类(和元组)的元素,但它们的输入为Any,所以会有些痛苦。