2010-11-25 5 views
3

J'ai une situation où j'ai quelques classes de cas où toutes leurs variables sont facultatives.Pliage sur les classes de cas

Disons que j'ai:

case class Size(width: Option[Int], height: Option[Int]) 
case class Foo(a: Option[String], b: Option[Boolean], c: Option[Char]) 

donné une collection du même type de classe de cas, je voudrais replier en les comparant les valeurs des options et conserver les valeurs qui sont définies. C'est à dire. pour Size:

values.foldLeft(x) { (a, b) => 
    Size(a.width.orElse(b.width), a.height.orElse(b.height)) 
} 

Je voudrais faire cela d'une manière plus générale pour l'une des classes de cas comme ceux ci-dessus. Je pense à faire quelque chose avec unapply(_).get etc. Est-ce que quelqu'un sait une façon intelligente de résoudre ce problème?

Répondre

2

Ok, considérez ceci:

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

On pourrait surcharger foldCase d'accepter deux, trois ou plusieurs paramètres, une version de f pour chaque arité. Il pourrait alors être utilisé avec n'importe quelle classe de cas. Comme il y a un problème à résoudre, voici une façon de le faire fonctionner avec des classes de cas ou deux paramètres. L'étendre à plus de paramètres est alors trivial, bien qu'un peu fatigant.

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) 

Pour l'utiliser en surcharge, il suffit de mettre toutes les définitions à l'intérieur d'un objet:

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

Lorsque vous faites cela, cependant, vous devrez tourner explicitement apply et unapply en fonctions:

case class Question(answer: Option[Boolean]) 
val list2 = List(Question(None), Question(Some(true)), Question(Some(false))) 
Folder.foldCase(Question.unapply _, Question.apply _)(list2) 

Il pourrait être possible de le transformer en un type structurel, de sorte que vous avez seulement besoin de passer l'objet compagnon, mais je ne pouvais pas le faire. Sur #scala, on m'a dit que la réponse est un non définitif, au moins à la façon dont j'ai abordé le problème.

+0

Merci Daniel, ça a l'air vraiment sympa! Je vais l'essayer. – chrsan

1

Vous pouvez utiliser productElement ou productIterator (sur scala.Product) pour récupérer/itérer génériquement les éléments des classes de cas (et tuples), mais ils sont saisi comme Tout, donc il y aura une certaine douleur.

+0

C'est ainsi que j'ai abordé le problème, mais je veux vraiment garder les types. – chrsan

2

[Code mis à jour]

Voici une solution qui ne nécessite qu'une seule classe abstraite par « arité »:

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

Notez que l'argument <:< implicite donnera une erreur de compilation lorsque d'autres classes de cas avec le même constructeur, les arguments sont passés à la méthode. Cependant, un constructeur "bien formé" est nécessaire, sinon le code de réflexion va exploser.

+0

Sauf pour le type de chose que vous mentionnez, cela semble vraiment bien. Merci Landei! – chrsan

+0

Les problèmes de frappe semblent être corrigés. – Landei

+0

Cool! Je vais l'essayer à la fois. – chrsan