web-dev-qa-db-fra.com

Faire correspondre plusieurs classes de cas dans scala

Je fais correspondre des classes de cas et j'aimerais traiter deux cas de la même manière. Quelque chose comme ça:

abstract class Foo
case class A extends Foo
case class B(s:String) extends Foo
case class C(s:String) extends Foo


def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case B(sb) | C(sc) => "B"
    case _ => "default"
  }
}

Mais quand je fais cela, j'obtiens l'erreur:

(fragment of test.scala):10: error: illegal variable in pattern alternative
    case B(sb) | C(sc) => "B"

Je peux le faire fonctionner, je supprime les paramètres de la définition de B et C, mais comment puis-je correspondre aux paramètres?

97
timdisney

On dirait que vous ne vous souciez pas des valeurs des paramètres String et que vous voulez traiter B et C de la même façon, alors:

def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case B(_) | C(_) => "B"
    case _ => "default"
  }
}

Si vous devez, devez, devez extraire le paramètre et le traiter dans le même bloc de code, vous pouvez:

def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case bOrC @ (B(_) | C(_)) => {
      val s = bOrC.asInstanceOf[{def s: String}].s // ugly, ugly
      "B(" + s + ")"
    }
    case _ => "default"
  }
}

Bien que j'estime qu'il serait beaucoup plus propre de prendre cela en compte dans une méthode:

def doB(s: String) = { "B(" + s + ")" }

def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case B(s) => doB(s)
    case C(s) => doB(s)
    case _ => "default"
  }
}
141
Mitch Blevins

Je peux voir différentes manières d’atteindre ce que vous recherchez, si vous avez des points communs entre les classes de cas. La première consiste à faire en sorte que les classes de cas étendent un trait qui déclare les points communs, la seconde consiste à utiliser un type de structure qui supprime la nécessité d'étendre vos classes de cas.

 object MuliCase {
   abstract class Foo
   case object A extends Foo

   trait SupportsS {val s: String}

   type Stype = Foo {val s: String}

   case class B(s:String) extends Foo
   case class C(s:String) extends Foo

   case class D(s:String) extends Foo with SupportsS
   case class E(s:String) extends Foo with SupportsS

   def matcher1(l: Foo): String = {
     l match {
       case A        => "A"
       case s: Stype => println(s.s); "B"
       case _        => "default"
     }
   }

   def matcher2(l: Foo): String = {
     l match {
       case A            => "A"
       case s: SupportsS => println(s.s); "B"
       case _            => "default"
     }
   }

   def main(args: Array[String]) {
     val a = A
     val b = B("B's s value")
     val c = C("C's s value")

     println(matcher1(a))
     println(matcher1(b))
     println(matcher1(c))

     val d = D("D's s value")
     val e = E("E's s value")

     println(matcher2(d))
     println(matcher2(e))
   }
 }

La méthode de type structurel génère un avertissement à propos de l'effacement, mais je ne suis pas sûr de savoir comment le supprimer.

9
Don Mackenzie

Eh bien, ça n'a pas vraiment de sens, n'est-ce pas? B et C s'excluent mutuellement. Sb ou sc sont donc liés, mais vous ne savez pas lequel. Vous aurez donc besoin d'une logique de sélection supplémentaire pour décider laquelle utiliser (étant donné qu'ils étaient liés à une Option [String], pas un string). Donc, il n'y a rien gagné à ce sujet:

  l match {
    case A() => "A"
    case B(sb) => "B(" + sb + ")"
    case C(sc) => "C(" + sc + ")"
    case _ => "default"
  }

Ou ca:

  l match {
    case A() => "A"
    case _: B => "B"
    case _: C => "C"
    case _ => "default"
  }
6
Randall Schulz