Ordnen Sie mehrere Fallklassen in Scala zu

99

Ich mache Matching gegen einige Fallklassen und möchte zwei der Fälle auf die gleiche Weise behandeln. Etwas wie das:

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

Aber wenn ich das mache, bekomme ich den Fehler:

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

Ich kann es zum Laufen bringen, indem ich die Parameter aus der Definition von B und C entferne, aber wie kann ich mit den Parametern übereinstimmen?

timdisney
quelle

Antworten:

144

Sieht so aus, als ob Sie sich nicht um die Werte der String-Parameter kümmern und B und C gleich behandeln möchten.

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

Wenn Sie den Parameter extrahieren und im selben Codeblock behandeln müssen, können Sie:

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

Obwohl ich der Meinung bin, dass es viel sauberer wäre, dies in eine Methode einzubeziehen:

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
quelle
Obwohl mein Beispiel es nicht zeigt, brauche ich diese Parameter. Sieht so aus, als müsste ich nur ein Objekt verwenden. Vielen Dank!
Timdisney
4
Gibt es einen Grund, warum Scala "Fall A (aString) | Fall B (aString) => println (aString)" nicht zulässt? Solange der Typ von aString für A und B identisch ist, sollte dies zulässig sein. Ihr letztes Beispiel scheint besser dran zu sein, die B- und C-Fälle nicht zu duplizieren.
James Moore
36
Ich gehe noch einen Schritt weiter. Ich denke, es wäre schön, case A(x) | B(x) => println(x)erlaubt zu sein, wenn der Typ von xim Typensystem von A (x) und B (x) auf die Obergrenze gesetzt wird.
Mitch Blevins
1
@MitchBlevins: Sie können für Issues abstimmen.scala-lang.org/browse/SUGGEST-25 (Variablenbindung in alternativem Muster zulassen)
Erik Kaplun
1
Für diejenigen, die sich fragen, was zum Teufel das @ -Symbol dort tut: scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html
SilentDirge
9

Es gibt mehrere Möglichkeiten, wie ich sehen kann, was Sie suchen, wenn Sie Gemeinsamkeiten zwischen Fallklassen haben. Die erste besteht darin, dass die Fallklassen ein Merkmal erweitern, das die Gemeinsamkeit deklariert, und die zweite darin, einen Strukturtyp zu verwenden, der die Notwendigkeit beseitigt, Ihre Fallklassen zu erweitern.

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

Die strukturelle Typmethode generiert eine Warnung vor dem Löschen, die ich derzeit nicht sicher beseitigen kann.

Don Mackenzie
quelle
6

Nun, es macht nicht wirklich Sinn, oder? B und C schließen sich gegenseitig aus, sodass entweder sb oder sc gebunden werden, aber Sie wissen nicht, welche. Daher benötigen Sie eine weitere Auswahllogik, um zu entscheiden, welche verwendet werden soll (vorausgesetzt, sie sind an eine Option [String] gebunden, nicht ein Faden). Daraus wird also nichts gewonnen:

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

Oder dieses:

  l match {
    case A() => "A"
    case _: B => "B"
    case _: C => "C"
    case _ => "default"
  }
Randall Schulz
quelle
Was ist, wenn es Ihnen egal ist, ob B oder C übereinstimmen? Sagen Sie im folgenden Code: args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }Ich sehe jedoch, dass dies nicht der übliche Fall ist und dass das Erstellen einer lokalen Methode eine Alternative ist. Wenn die Alternative jedoch zweckmäßig ist, macht es wenig Sinn, Fallalternativen zu haben. Tatsächlich haben Sie in einigen ML-Dialekten eine ähnliche Funktion und können weiterhin Variablen binden, solange (IIRC) jede Variable bei beiden Alternativen mit demselben Typ gebunden ist.
Blaisorblade
Du hast Recht. Wenn Sie sich nur um die Typen und nicht um die Werte kümmern und auch nicht, welcher Typ dargestellt wurde, ist die disjunktive typbasierte Übereinstimmung sinnvoll und verfügbar.
Randall Schulz