Combine várias classes de casos em scala


99

Estou fazendo correspondência com algumas classes de caso e gostaria de tratar dois dos casos da mesma maneira. Algo assim:

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

Mas quando faço isso, recebo o erro:

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

Posso fazê-lo funcionar removendo os parâmetros da definição de B e C, mas como posso combinar com os parâmetros?

Respostas:


144

Parece que você não se importa com os valores dos parâmetros String e deseja tratar B e C da mesma forma, então:

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

Se você deve, deve, deve extrair o parâmetro e tratá-los no mesmo bloco de código, você pode:

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

Embora eu ache que seria muito mais limpo fatorar isso em um método:

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

Embora meu exemplo não mostre, estou precisando desses parâmetros. Parece que vou ter que usar um objeto. Obrigado!
timdisney

4
Existe uma razão pela qual o scala não permite "case A (aString) | case B (aString) => println (aString)"? Parece que, desde que o tipo de aString seja idêntico para A e B, ele deve ser permitido. Seu último exemplo parece que seria melhor não duplicar os casos B e C.
James Moore

36
Eu vou mais longe com você. Acho que seria bom ter case A(x) | B(x) => println(x)permitido onde o tipo de xé definido como o limite superior no sistema de tipos de tudo o que A (x) e B (x) produzem.
Mitch Blevins de

1
@MitchBlevins: você pode votar em issues.scala-lang.org/browse/SUGGEST-25 (permitir vinculação de variável em padrão alternativo)
Erik Kaplun

1
Para aqueles que estão se perguntando o que diabos o símbolo @ está fazendo lá: scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html
SilentDirge

9

Existem algumas maneiras que posso ver de alcançar o que você procura, se houver alguma semelhança entre as classes de caso. O primeiro é fazer com que as classes de caso estendam um traço que declara a comunalidade, o segundo é usar um tipo estrutural que remove a necessidade de estender suas classes de caso.

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

O método do tipo estrutural gera um aviso de apagamento que, no momento, não tenho certeza de como eliminar.


6

Bem, isso realmente não faz sentido, não é? B e C são mutuamente exclusivos, então sb ou sc são vinculados, mas você não sabe quais, então você precisaria de mais lógica de seleção para decidir qual usar (dado que eles foram vinculados a uma Option [String], não uma linha). Portanto, não há nada a ganhar com isso:

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

Ou isto:

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

E se você não se importar se B ou C foi correspondido? Diga no seguinte código: args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }No entanto, vejo que esse não é o caso comum e que criar um método local é uma alternativa. No entanto, se a alternativa for conveniente, não há sentido em ter alternativas de caso. Na verdade, em alguns dialetos de ML você tem um recurso semelhante e ainda pode vincular variáveis, desde que (IIRC) cada variável seja vinculada ao mesmo tipo em ambas as alternativas.
Blaisorblade

Você está certo. Se você se preocupa apenas com os tipos e não com os valores nem com qual tipo foi apresentado, a correspondência baseada em tipo disjuntiva é significativa e disponível.
Randall Schulz
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.