Сопоставление нескольких классов case в scala

100

Я выполняю сопоставление с некоторыми классами case и хотел бы обрабатывать два случая таким же образом. Что-то вроде этого:

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

Но когда я это делаю, я получаю сообщение об ошибке:

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

Я могу заставить его работать, я удаляю параметры из определения B и C, но как я могу сопоставить их с параметрами?

Тимдисней
источник

Ответы:

145

Похоже, вам не важны значения параметров String и вы хотите относиться к B и C одинаково, поэтому:

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

Если вы должны, должны, должны извлечь параметр и обработать их в одном блоке кода, вы можете:

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

Хотя я считаю, что было бы намного проще включить это в метод:

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"
  }
}
Митч Блевинс
источник
Хотя мой пример этого не показывает, мне нужны эти параметры. Похоже, мне просто нужно использовать объект. Спасибо!
Timdisney
4
Есть ли причина, по которой scala не разрешает "case A (aString) | case B (aString) => println (aString)"? Похоже, что до тех пор, пока тип aString идентичен как для A, так и для B, это должно быть разрешено. В вашем последнем примере кажется, что было бы лучше не дублировать случаи B и C.
Джеймс Мур
37
Я пойду дальше. Я думаю, было бы неплохо иметь case A(x) | B(x) => println(x)разрешение, когда тип xустановлен равным верхней границе в системе типов того, что производят A (x) и B (x).
Митч Блевинс
1
@MitchBlevins: вы можете голосовать за issues.scala-lang.org/browse/SUGGEST-25 (разрешить привязку переменных в альтернативном шаблоне)
Эрик Каплун
2
Для тех, кто задается вопросом, что, черт возьми, делает там символ @: scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html
SilentDirge
9

Есть несколько способов, которыми я могу добиться того, что вам нужно, если у вас есть некоторая общность между классами case. Во-первых, классы case расширяют признак, декларирующий общность, второй - использовать структурный тип, который устраняет необходимость в расширении классов case.

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

Метод структурного типа генерирует предупреждение о стирании, которое в настоящее время я не знаю, как устранить.

Дон Маккензи
источник
6

Что ж, в этом нет никакого смысла, не так ли? B и C являются взаимоисключающими, поэтому привязаны либо sb, либо sc, но вы не знаете, какой из них, поэтому вам потребуется дополнительная логика выбора, чтобы решить, что использовать (учитывая, что они были привязаны к Option [String], а не строка). Так что от этого ничего не добьешься:

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

Или это:

  l match {
    case A() => "A"
    case _: B => "B"
    case _: C => "C"
    case _ => "default"
  }
Рэндалл Шульц
источник
Что делать, если вам все равно, совпадают ли B или C? Скажите в следующем коде: args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }Однако я вижу, что это не общий случай и альтернативой является создание локального метода. Однако, если альтернатива удобна, то нет смысла иметь альтернативные варианты. Фактически, в некоторых диалектах ML у вас есть аналогичная функция, и вы все еще можете связывать переменные, если (IIRC) каждая переменная связана с одним и тем же типом в обеих альтернативах.
Blaisorblade
Ты прав. Если вы заботитесь только о типах, а не о значениях или о том, какой тип был представлен, дизъюнктивное сопоставление на основе типов имеет смысл и доступно.
Randall Schulz