Dopasuj wiele klas przypadków w scali

100

Robię dopasowywanie do niektórych klas przypadków i chciałbym poradzić sobie z dwoma z nich w ten sam sposób. Coś takiego:

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

Ale kiedy to robię, pojawia się błąd:

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

Mogę sprawić, że będzie działać, usuwając parametry z definicji B i C, ale jak mogę dopasować je do parametrów?

timdisney
źródło

Odpowiedzi:

145

Wygląda na to, że nie przejmujesz się wartościami parametrów String i chcesz traktować B i C tak samo, więc:

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

Jeśli musisz, musisz, musisz wyodrębnić parametr i traktować je w tym samym bloku kodu, możesz:

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

Chociaż wydaje mi się, że byłoby znacznie czystsze, gdybyśmy uwzględniali to w metodzie:

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
źródło
Chociaż mój przykład tego nie pokazuje, potrzebuję tych parametrów. Wygląda na to, że będę musiał po prostu użyć obiektu. Dzięki!
timdisney,
4
Czy istnieje powód, dla którego scala nie dopuszcza "przypadku A (aString) | przypadku B (aString) => println (aString)"? Wydaje się, że tak długo, jak typ aString jest identyczny zarówno dla A, jak i B, powinien być dozwolony. Twój ostatni przykład wygląda na to, że lepiej nie powielać przypadków B i C.
James Moore
37
Pójdę dalej. Myślę, że byłoby miło mieć case A(x) | B(x) => println(x)pozwolenie, gdy typ xjest ustawiony na górną granicę w systemie typów wszystkiego, co produkują A (x) i B (x).
Mitch Blevins
1
@MitchBlevins: możesz głosować na problemy.scala-lang.org/browse/SUGGEST-25 (zezwalaj na zmienne wiązanie w alternatywnym wzorcu)
Erik Kaplun
2
Dla tych, którzy zastanawiają się, co tam do cholery robi symbol @: scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html
SilentDirge
9

Jest kilka sposobów na osiągnięcie tego, do czego dążysz, jeśli masz pewne podobieństwa między klasami przypadków. Pierwsza polega na tym, aby klasy przypadków rozszerzały cechę, która deklaruje podobieństwo, a druga to użycie typu strukturalnego, który eliminuje potrzebę rozszerzania klas przypadków.

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

Metoda typu strukturalnego generuje ostrzeżenie o skasowaniu, które obecnie nie jestem pewien, jak wyeliminować.

Don Mackenzie
źródło
6

Cóż, to naprawdę nie ma sensu, prawda? B i C wykluczają się wzajemnie, więc sb lub sc zostają powiązane, ale nie wiesz, które z nich, więc potrzebujesz dalszej logiki wyboru, aby zdecydować, którego użyć (zakładając, że były one powiązane z Option [String], a nie struna). Więc nic na tym nie zyskuje:

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

Albo to:

  l match {
    case A() => "A"
    case _: B => "B"
    case _: C => "C"
    case _ => "default"
  }
Randall Schulz
źródło
A jeśli nie obchodzi cię, czy dopasowano B lub C? Powiedz w następującym kodzie: args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }Jednak widzę, że to nie jest typowy przypadek i że utworzenie metody lokalnej jest alternatywą. Jeśli jednak alternatywa jest wygodna, nie ma sensu mieć alternatywnych przypadków. W rzeczywistości w niektórych dialektach ML masz podobną funkcję i nadal możesz wiązać zmienne, o ile (IIRC) każda zmienna jest powiązana z tym samym typem w obu alternatywach.
Blaisorblade
Masz rację. Jeśli zależy Ci tylko na typach, a nie na wartościach ani typie, który został przedstawiony, rozłączne dopasowanie oparte na typach jest znaczące i dostępne.
Randall Schulz