Trova più classi di casi in scala


100

Sto eseguendo la corrispondenza con alcune classi di casi e vorrei gestire due dei casi allo stesso modo. Qualcosa come questo:

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

Ma quando lo faccio ottengo l'errore:

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

Posso farlo funzionare rimuovendo i parametri dalla definizione di B e C ma come posso abbinarli ai parametri?

Risposte:


145

Sembra che non ti interessi dei valori dei parametri String e desideri trattare B e C allo stesso modo, quindi:

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

Se devi, devi, devi estrarre i parametri e trattarli nello stesso blocco di codice, potresti:

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

Anche se penso che sarebbe molto più pulito includerlo in un metodo:

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

Anche se il mio esempio non lo mostra, ho bisogno di quei parametri. Sembra che dovrò solo usare un oggetto. Grazie!
timdisney

4
C'è una ragione per cui scala non consente "case A (aString) | case B (aString) => println (aString)"? Sembra che fintanto che il tipo di aString è identico sia per A che per B, dovrebbe essere consentito. Il tuo ultimo esempio sembra che sarebbe meglio non duplicare i casi B e C.
James Moore

37
Ti vado oltre. Penso che sarebbe bello avere il case A(x) | B(x) => println(x)permesso dove il tipo di xè impostato sul limite superiore nel sistema di tipi di qualsiasi cosa producano A (x) e B (x).
Mitch Blevins

1
@ MitchBlevins: puoi votare per issues.scala-lang.org/browse/SUGGEST-25 (consentire l'associazione di variabili in un modello alternativo)
Erik Kaplun

2
Per coloro che si chiedono cosa diavolo ci fa il simbolo @: scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html
SilentDirge

9

Ci sono un paio di modi in cui posso vedere per ottenere ciò che stai cercando, se hai alcuni punti in comune tra le classi di casi. Il primo è fare in modo che le classi case estendano un tratto che dichiara la comunanza, il secondo è usare un tipo strutturale che elimina la necessità di estendere le classi 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))
   }
 }

Il metodo di tipo strutturale genera un avviso di cancellazione che, al momento, non so come eliminare.


6

Beh, non ha davvero senso, vero? B e C si escludono a vicenda, quindi sb o sc vengono associati, ma non sai quale, quindi avresti bisogno di un'ulteriore logica di selezione per decidere quale usare (dato che erano vincolati a un'opzione [String], non una stringa). Quindi non c'è niente da guadagnare su questo:

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

O questo:

  l match {
    case A() => "A"
    case _: B => "B"
    case _: C => "C"
    case _ => "default"
  }

E se non ti interessa se B o C sono stati abbinati? Dì nel codice seguente: args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }Tuttavia, vedo che non è il caso comune e che la creazione di un metodo locale è un'alternativa. Tuttavia, se l'alternativa è conveniente, non ha molto senso avere alternative per i casi. In realtà, in alcuni dialetti ML hai una caratteristica simile e puoi ancora legare variabili, a patto che (IIRC) ogni variabile sia associata con lo stesso tipo su entrambe le alternative.
Blaisorblade

Hai ragione. Se ti interessano solo i tipi e non i valori né il tipo presentato, la corrispondenza disgiuntiva basata sul tipo è significativa e disponibile.
Randall Schulz
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.