Khớp nhiều lớp trường hợp trong scala


99

Tôi đang đối sánh với một số lớp trường hợp và muốn xử lý hai trong số các trường hợp theo cùng một cách. Một cái gì đó như thế này:

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

Nhưng khi tôi làm điều này, tôi gặp lỗi:

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

Tôi có thể làm cho nó hoạt động Tôi xóa các tham số khỏi định nghĩa của B và C nhưng làm thế nào tôi có thể khớp với các tham số?

Câu trả lời:


144

Có vẻ như bạn không quan tâm đến giá trị của các tham số Chuỗi và muốn xử lý B và C giống nhau, vì vậy:

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

Nếu bạn phải, phải, phải trích xuất tham số và xử lý chúng trong cùng một khối mã, bạn có thể:

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

Mặc dù tôi cảm thấy sẽ sạch hơn nhiều nếu đưa điều đó vào một phương pháp:

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

Mặc dù ví dụ của tôi không cho thấy tôi đang cần những thông số đó. Có vẻ như tôi sẽ phải sử dụng một đối tượng. Cảm ơn!
timdisney

4
Có lý do gì mà scala không cho phép "case A (aString) | case B (aString) => println (aString)" không? Có vẻ như miễn là loại aString giống hệt nhau cho cả A và B, nó sẽ được cho phép. Ví dụ cuối cùng của bạn có vẻ như tốt hơn là không sao chép các trường hợp B và C.
James Moore

36
Tôi sẽ đi xa hơn với bạn. Tôi nghĩ rằng sẽ rất tuyệt nếu case A(x) | B(x) => println(x)được phép trong đó kiểu của xđược đặt thành giới hạn trên trong hệ thống kiểu của bất kỳ sản phẩm A (x) và B (x) nào.
Mitch Blevins

1
@MitchBlevins: bạn có thể bỏ phiếu cho issues.scala-lang.org/browse/SUGGEST-25 (cho phép biến ràng buộc trong mô hình thay thế)
Erik Kaplun

1
Đối với những người tự hỏi biểu tượng @ đang làm cái quái gì trong đó: scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html
SilentDirge

9

Có một số cách mà tôi có thể thấy để đạt được những gì bạn đang có, nếu bạn có một số điểm chung giữa các lớp trường hợp. Đầu tiên là để các lớp trường hợp mở rộng một đặc điểm khai báo tính chung, thứ hai là sử dụng kiểu cấu trúc loại bỏ nhu cầu mở rộng các lớp trường hợp của bạn.

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

Phương thức loại cấu trúc tạo ra một cảnh báo về việc tẩy xóa, hiện tại tôi không chắc chắn về cách loại bỏ.


6

Chà, nó không thực sự có ý nghĩa, phải không? B và C loại trừ lẫn nhau, do đó, sb hoặc sc đều bị ràng buộc, nhưng bạn không biết cái nào, vì vậy bạn cần logic lựa chọn thêm để quyết định cái nào sẽ sử dụng (vì chúng bị ràng buộc với một Tùy chọn [Chuỗi], không một chuỗi). Vì vậy, không thu được gì về điều này:

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

Hoặc cái này:

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

Điều gì xảy ra nếu bạn không quan tâm liệu B hoặc C có được khớp hay không? Nói đoạn mã sau: args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }Tuy nhiên, tôi thấy đó không phải là trường hợp phổ biến và việc tạo phương thức cục bộ là một giải pháp thay thế. Tuy nhiên, nếu phương án thay thế thuận tiện, thì việc có các phương án thay thế trường hợp là rất ít. Trên thực tế, trong một số phương ngữ ML, bạn có một tính năng tương tự và bạn vẫn có thể liên kết các biến, miễn là (IIRC) vì mỗi biến được ràng buộc với cùng một kiểu trên cả hai lựa chọn thay thế.
Blaisorblade,

Bạn nói đúng. Nếu bạn chỉ quan tâm đến các kiểu chứ không phải các giá trị cũng như kiểu nào được trình bày, thì đối sánh dựa trên kiểu không kết hợp là có ý nghĩa và có sẵn.
Randall Schulz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.