2010-11-25 70 views
3

我有一個情況下,我有幾個案例類,其中所有的變量是可選的。摺疊案例類

比方說,我有:

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等做一些事情。有誰知道一個聰明的方法來解決這個問題嗎?

回答

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接受兩個,三個或多個參數,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上,我被告知答案是肯定的否,至少是我如何處理這個問題。

+0

謝謝丹尼爾,這看起來真不錯!將嘗試一下。 – chrsan 2010-11-26 12:41:23

1

您可以使用productElementproductIterator(上scala.Product)一般地檢索/迭代case類(和元組)的元素,但他們類型爲所有,所以會有一些疼痛。

+0

這是我第一次遇到這個問題,但我真的想保留這些類型。 – chrsan 2010-11-26 12:42:58

2

[代碼更新]

這裏是一個需要每個「元數」只有一個抽象類的溶液:

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)) 

注意,隱式<:<參數將給出一個編譯時間錯誤,當其他情況下,類使用相同的構造函數參數傳遞給方法。

然而,一個「格式良好」的構造函數是必需的,否則反射代碼將炸燬。

+0

除了你提到的類型的東西,這看起來非常好。謝謝Landei! – chrsan 2010-11-26 12:44:56