Збігайте кілька класів випадків у масштабі


100

Я співставляю деякі класи випадків і хотів би опрацювати два випадки однаково. Щось на зразок цього:

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
Чи є причина, що скала не дозволяє "випадок A (aString) | випадок 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

Є кілька способів, які я бачу, щоб досягти того, що ти шукаєш, якщо ти маєш спільність між класами справ. Перший полягає в тому, щоб класи кейсів розширювали ознаку, яка декларує спільність, а друга - використовувати структурний тип, який знімає необхідність розширення ваших класів випадків.

 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

Ви праві. Якщо ви дбаєте лише про типи, а не про значення, а також про те, який тип був представлений, диз'юнктивна відповідність типу є значущою та доступною.
Рандалл Шульц
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.