Faire correspondre plusieurs classes de cas dans scala

100

Je fais des comparaisons avec certaines classes de cas et je voudrais gérer deux des 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 en supprimant les paramètres de la définition de B et C mais comment puis-je faire correspondre les paramètres?

Timdisney
la source

Réponses:

145

Il semble que vous ne vous souciez pas des valeurs des paramètres String et que vous voulez traiter B et C de la même manière, donc:

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 je pense qu'il serait beaucoup plus propre d'en tenir 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"
  }
}
Mitch Blevins
la source
Bien que mon exemple ne le montre pas, j'ai besoin de ces paramètres. On dirait que je vais juste devoir utiliser un objet. Merci!
timdisney
4
Y a-t-il une raison pour laquelle scala n'autorise pas "case A (aString) | case B (aString) => println (aString)"? On dirait que tant que le type de aString est identique pour A et B, il devrait être autorisé. Votre dernier exemple semble qu'il serait préférable de ne pas dupliquer les cas B et C.
James Moore
37
Je vais vous aller plus loin. Je pense que ce serait bien de pouvoir case A(x) | B(x) => println(x)être autorisé lorsque le type de xest défini sur la limite supérieure dans le système de types de tout ce que A (x) et B (x) produisent.
Mitch Blevins
1
@MitchBlevins: vous pouvez voter pour issues.scala-lang.org/browse/SUGGEST-25 (autoriser la liaison de variable dans un modèle alternatif)
Erik Kaplun
2
Pour ceux qui se demandent ce que fait le symbole @ là- dedans
SilentDirge
9

Il y a deux façons que je peux voir pour réaliser ce que vous recherchez, si vous avez des points communs entre les classes de cas. La première consiste à demander aux classes de cas d'étendre un trait qui déclare la similitude, la seconde est d'utiliser un type structurel qui supprime le besoin 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 sur l'effacement qui, pour le moment, je ne sais pas comment éliminer.

Don Mackenzie
la source
6

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

  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"
  }
Randall Schulz
la source
Que faire si vous ne vous souciez pas de savoir si B ou C correspondaient? Dites dans le code suivant: args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }Cependant, je vois que ce n'est pas le cas courant et que la création d'une méthode locale est une alternative. Cependant, si l'alternative est pratique, il ne sert à rien d'avoir des alternatives de cas. En fait, dans certains dialectes ML, vous avez une fonctionnalité similaire et vous pouvez toujours lier des variables, tant (IIRC) que chaque variable est liée avec le même type sur les deux alternatives.
Blaisorblade
Vous avez raison. Si vous ne vous souciez que des types et non des valeurs ou du type présenté, la correspondance disjonctive basée sur le type est significative et disponible.
Randall Schulz le