Làm thế nào để tôi có được xung quanh loại tẩy trên Scala? Hoặc, tại sao tôi không thể có được tham số loại của các bộ sưu tập của mình?


370

Một thực tế đáng buồn của Scala là nếu bạn khởi tạo Danh sách [Int], bạn có thể xác minh rằng cá thể của bạn là Danh sách và bạn có thể xác minh rằng bất kỳ yếu tố riêng lẻ nào của nó là Int, nhưng không phải là Danh sách [ Int], như có thể dễ dàng xác minh:

scala> List(1,2,3) match {
     | case l : List[String] => println("A list of strings?!")
     | case _ => println("Ok")
     | }
warning: there were unchecked warnings; re-run with -unchecked for details
A list of strings?!

Tùy chọn -unchecked đặt sự đổ lỗi thẳng vào loại tẩy:

scala>  List(1,2,3) match {
     |  case l : List[String] => println("A list of strings?!")
     |  case _ => println("Ok")
     |  }
<console>:6: warning: non variable type-argument String in type pattern is unchecked since it is eliminated by erasure
        case l : List[String] => println("A list of strings?!")
                 ^
A list of strings?!

Tại sao vậy, và làm thế nào để tôi có được xung quanh nó?


Scala 2.8 Beta 1 RC4 vừa thực hiện một số thay đổi về cách thức hoạt động của loại tẩy xóa. Tôi không chắc chắn nếu điều này ảnh hưởng trực tiếp đến câu hỏi của bạn.
Scott Morrison

1
Đó chỉ là những loại tẩy xoá để , đã thay đổi. Tóm tắt của nó có thể được tóm tắt là " Đề xuất: Việc xóa" Đối tượng với A "là" A "thay vì" Đối tượng ". " Thông số kỹ thuật thực tế khá phức tạp. Dù sao, đó là về mixins, và câu hỏi này liên quan đến thuốc generic.
Daniel C. Sobral

Cảm ơn đã làm rõ - Tôi là một người mới đến Scala. Tôi cảm thấy như bây giờ là thời điểm tồi tệ để nhảy vào Scala. Trước đó, tôi có thể đã học được những thay đổi trong 2.8 từ một cơ sở tốt, sau này tôi không bao giờ phải biết sự khác biệt!
Scott Morrison

1
Đây là một câu hỏi hơi liên quan về TypeTags .
pvorb

2
Chạy scala 2.10.2, tôi thấy cảnh báo này thay vào đó: <console>:9: warning: fruitless type test: a value of type List[Int] cannot also be a List[String] (but still might match its erasure) case list: List[String] => println("a list of strings?") ^Tôi thấy câu hỏi và câu trả lời của bạn rất hữu ích, nhưng tôi không chắc cảnh báo cập nhật này có hữu ích cho độc giả hay không.
Kevin Meredith

Câu trả lời:


243

Câu trả lời này sử dụng Manifest-API, không được dùng trong Scala 2.10. Xin vui lòng xem câu trả lời dưới đây để biết thêm các giải pháp hiện tại.

Scala được định nghĩa với Loại xóa vì Máy ảo Java (JVM), không giống như Java, không nhận được tổng quát. Điều này có nghĩa là, trong thời gian chạy, chỉ có lớp tồn tại, không phải là tham số loại của nó. Trong ví dụ này, JVM biết nó đang xử lý a scala.collection.immutable.List, nhưng không phải danh sách này được tham số hóa vớiInt .

May mắn thay, có một tính năng trong Scala cho phép bạn khắc phục điều đó. Đó là biểu hiện . Một Manifest là lớp có các thể hiện là các đối tượng đại diện cho các loại. Vì các thể hiện này là các đối tượng, bạn có thể chuyển chúng xung quanh, lưu trữ chúng và thường gọi các phương thức trên chúng. Với sự hỗ trợ của các tham số ngầm, nó trở thành một công cụ rất mạnh. Lấy ví dụ sau đây, ví dụ:

object Registry {
  import scala.reflect.Manifest

  private var map= Map.empty[Any,(Manifest[_], Any)] 

  def register[T](name: Any, item: T)(implicit m: Manifest[T]) {
    map = map.updated(name, m -> item)
  }

  def get[T](key:Any)(implicit m : Manifest[T]): Option[T] = {
    map get key flatMap {
      case (om, s) => if (om <:< m) Some(s.asInstanceOf[T]) else None
    }     
  }
}

scala> Registry.register("a", List(1,2,3))

scala> Registry.get[List[Int]]("a")
res6: Option[List[Int]] = Some(List(1, 2, 3))

scala> Registry.get[List[String]]("a")
res7: Option[List[String]] = None

Khi lưu trữ một phần tử, chúng tôi cũng lưu trữ "Bản kê khai" của phần tử đó. Manifest là một lớp có các thể hiện đại diện cho các kiểu Scala. Các đối tượng này có nhiều thông tin hơn JVM, cho phép chúng tôi kiểm tra loại đầy đủ, tham số hóa.

Lưu ý, tuy nhiên, a Manifestvẫn là một tính năng đang phát triển. Như một ví dụ về những hạn chế của nó, hiện tại nó không biết gì về phương sai và cho rằng mọi thứ đều là đồng biến thể. Tôi hy vọng nó sẽ ổn định và vững chắc hơn một khi thư viện phản chiếu Scala, hiện đang được phát triển, hoàn thành.


3
Các getphương pháp có thể được định nghĩa là for ((om, v) <- _map get key if om <:< m) yield v.asInstanceOf[T].
Aaron Novstrup

4
@Aaron Đề xuất rất tốt, nhưng tôi sợ nó có thể che khuất mã cho những người tương đối mới với Scala. Tôi đã không có nhiều kinh nghiệm với Scala khi tôi viết mã đó, đôi khi trước khi tôi đặt nó vào câu hỏi / câu trả lời này.
Daniel C. Sobral

6
@KimStebel Bạn có biết rằng TypeTagthực sự được sử dụng tự động trên khớp mẫu? Tuyệt, hả?
Daniel C. Sobral

1
Mát mẻ! Có lẽ bạn nên thêm nó vào câu trả lời.
Kim Stebel

1
Để trả lời câu hỏi của riêng tôi ngay phía trên: Có, trình biên dịch tạo Manifestchính param, xem: stackoverflow.com/a/11495793/694469 "ví dụ [manifest / type-tag] [...] đang được trình biên dịch ngầm tạo "
KajMagnus

96

Bạn có thể thực hiện việc này bằng TypeTags (như Daniel đã đề cập, nhưng tôi sẽ chỉ đánh vần rõ ràng):

import scala.reflect.runtime.universe._
def matchList[A: TypeTag](list: List[A]) = list match {
  case strlist: List[String @unchecked] if typeOf[A] =:= typeOf[String] => println("A list of strings!")
  case intlist: List[Int @unchecked] if typeOf[A] =:= typeOf[Int] => println("A list of ints!")
}

Bạn cũng có thể thực hiện việc này bằng ClassTags (giúp bạn không phải phụ thuộc vào phản xạ scala):

import scala.reflect.{ClassTag, classTag}
def matchList2[A : ClassTag](list: List[A]) = list match {
  case strlist: List[String @unchecked] if classTag[A] == classTag[String] => println("A List of strings!")
  case intlist: List[Int @unchecked] if classTag[A] == classTag[Int] => println("A list of ints!")
}

ClassTags có thể được sử dụng miễn là bạn không mong đợi tham số loại A chính nó là một loại chung.

Thật không may, nó hơi dài dòng và bạn cần chú thích @unchecked để chặn cảnh báo của trình biên dịch. TypeTag có thể được trình biên dịch tự động kết hợp vào mẫu phù hợp trong tương lai: https://issues.scala-lang.org/browse/SI-6517


2
Điều gì về việc loại bỏ không cần thiết [List String @unchecked]vì nó không thêm bất cứ điều gì vào mẫu này phù hợp (Chỉ cần sử dụng case strlist if typeOf[A] =:= typeOf[String] =>sẽ làm điều đó hoặc ngay cả case _ if typeOf[A] =:= typeOf[String] =>khi biến bị ràng buộc là không cần thiết trong phần thân của case).
Nader Ghanbari

1
Tôi đoán rằng nó sẽ làm việc cho ví dụ đã cho nhưng tôi nghĩ rằng hầu hết các cách sử dụng thực sự sẽ có lợi khi có loại yếu tố.
tksfz

Trong các ví dụ ở trên, phần không được kiểm tra trước điều kiện bảo vệ có thực hiện không? Bạn sẽ không có ngoại lệ trong lớp khi trải qua các trận đấu trên đối tượng đầu tiên không thể được truyền thành chuỗi?
Toby

Hừm tôi tin rằng không có diễn viên nào trước khi áp dụng bộ bảo vệ - bit không được kiểm tra là loại không cho đến khi mã bên phải của mã =>được thực thi. (Và khi mã trên RHS được thực hiện, các lính canh cung cấp một đảm bảo tĩnh vào loại các yếu tố Có thể có một dàn diễn viên ở đó, nhưng nó an toàn..)
tksfz

Liệu giải pháp này tạo ra chi phí thời gian chạy đáng kể?
stanislav.chetvertkov

65

Bạn có thể sử dụng Typeablelớp loại từ hình dạng để có được kết quả mà bạn theo sau,

Phiên REPL mẫu,

scala> import shapeless.syntax.typeable._
import shapeless.syntax.typeable._

scala> val l1 : Any = List(1,2,3)
l1: Any = List(1, 2, 3)

scala> l1.cast[List[String]]
res0: Option[List[String]] = None

scala> l1.cast[List[Int]]
res1: Option[List[Int]] = Some(List(1, 2, 3))

Các casthoạt động sẽ được xóa càng chính xác càng tốt với các Typeabletrường hợp trong phạm vi có sẵn.


14
Cần lưu ý rằng hoạt động "diễn viên" sẽ đệ quy đi qua toàn bộ bộ sưu tập và các bộ sưu tập của nó và kiểm tra xem tất cả các giá trị liên quan có đúng loại không. (Tức là, l1.cast[List[String]]đại khái for (x<-l1) assert(x.isInstanceOf[String]) Đối với các cơ sở dữ liệu lớn hoặc nếu các diễn viên xảy ra rất thường xuyên, đây có thể là một chi phí không thể chấp nhận được.
Dominique Unruh

16

Tôi đã đưa ra một giải pháp tương đối đơn giản, đủ cho các tình huống sử dụng hạn chế, về cơ bản là bọc các kiểu tham số hóa sẽ gặp phải vấn đề xóa kiểu trong các lớp bao bọc có thể được sử dụng trong câu lệnh khớp.

case class StringListHolder(list:List[String])

StringListHolder(List("str1","str2")) match {
    case holder: StringListHolder => holder.list foreach println
}

Điều này có đầu ra dự kiến ​​và giới hạn nội dung của lớp trường hợp của chúng tôi ở loại mong muốn, Danh sách chuỗi.

Thêm chi tiết tại đây: http://www.scalafied.com/?p=60


14

Có một cách để khắc phục vấn đề xóa kiểu trong Scala. Trong Khắc phục Loại xóa trong Kết hợp 1Khắc phục Loại khắc phục trong Kết hợp 2 (Phương sai) là một số giải thích về cách mã hóa một số người trợ giúp để bọc các loại, bao gồm cả Phương sai, cho phù hợp.


Điều này không khắc phục được loại tẩy. Trong ví dụ của anh ấy, làm val x: Any = List (1,2,3); x khớp {case IntList (l) => println (s "Khớp $ {l (1)}"); case _ => println (s "Không khớp")} tạo ra "Không khớp"
user48956

bạn có thể có một cái nhìn về các macro scala 2.10.
Alex

11

Tôi tìm thấy một cách giải quyết tốt hơn một chút cho giới hạn của ngôn ngữ tuyệt vời này.

Trong Scala, vấn đề xóa kiểu không xảy ra với mảng. Tôi nghĩ rằng nó dễ dàng hơn để chứng minh điều này với một ví dụ.

Hãy để chúng tôi nói rằng chúng tôi có một danh sách (Int, String), sau đó đưa ra một cảnh báo xóa

x match {
  case l:List[(Int, String)] => 
  ...
}

Để giải quyết vấn đề này, trước tiên hãy tạo một lớp trường hợp:

case class IntString(i:Int, s:String)

sau đó trong khớp mẫu làm một cái gì đó như:

x match {
  case a:Array[IntString] => 
  ...
}

mà dường như làm việc hoàn hảo.

Điều này sẽ yêu cầu các thay đổi nhỏ trong mã của bạn để làm việc với các mảng thay vì danh sách, nhưng không phải là một vấn đề lớn.

Lưu ý rằng việc sử dụng case a:Array[(Int, String)]vẫn sẽ đưa ra cảnh báo xóa kiểu, do đó cần phải sử dụng lớp container mới (trong ví dụ này IntString).


10
"giới hạn của ngôn ngữ tuyệt vời khác" đó là một hạn chế của Scala và nhiều hạn chế hơn của JVM. Có lẽ Scala có thể đã được thiết kế để bao gồm thông tin kiểu khi nó chạy trên JVM, nhưng tôi không nghĩ một thiết kế như thế sẽ bảo toàn khả năng tương tác với Java (nghĩa là, như được thiết kế, bạn có thể gọi Scala từ Java.)
Carl G

1
Theo dõi, hỗ trợ cho các thế hệ thống nhất cho Scala trong .NET / CLR là một khả năng đang diễn ra.
Carl G

6

Vì Java không biết loại phần tử thực tế, tôi thấy nó hữu ích nhất khi chỉ sử dụng List[_]. Sau đó, cảnh báo biến mất và mã mô tả thực tế - đó là một danh sách của một cái gì đó chưa biết.


4

Tôi tự hỏi nếu đây là một cách giải quyết phù hợp:

scala> List(1,2,3) match {
     |    case List(_: String, _*) => println("A list of strings?!")
     |    case _ => println("Ok")
     | }

Nó không khớp với trường hợp "danh sách trống", nhưng nó đưa ra một lỗi biên dịch, không phải là một cảnh báo!

error: type mismatch;
found:     String
requirerd: Int

Mặt khác, nó có vẻ hoạt động ....

scala> List(1,2,3) match {
     |    case List(_: Int, _*) => println("A list of ints")
     |    case _ => println("Ok")
     | }

Không phải là nó thậm chí còn tốt hơn hay tôi đang thiếu điểm ở đây?


3
Không hoạt động với Danh sách (1, "a", "b"), có loại Danh sách [Bất kỳ]
sullivan-

1
Mặc dù quan điểm của sullivan là chính xác và có những vấn đề liên quan đến thừa kế, tôi vẫn thấy điều này hữu ích.
Seth


0

Tôi muốn thêm một câu trả lời khái quát vấn đề vào: Làm thế nào để có được một chuỗi đại diện cho loại danh sách của tôi khi chạy

import scala.reflect.runtime.universe._

def whatListAmI[A : TypeTag](list : List[A]) = {
    if (typeTag[A] == typeTag[java.lang.String]) // note that typeTag[String] does not match due to type alias being a different type
        println("its a String")
    else if (typeTag[A] == typeTag[Int])
        println("its a Int")

    s"A List of ${typeTag[A].tpe.toString}"
}

val listInt = List(1,2,3)
val listString = List("a", "b", "c")

println(whatListAmI(listInt))
println(whatListAmI(listString))

-18

Sử dụng mô hình bảo vệ khớp

    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }

4
Lý do tại sao cái này sẽ không hoạt động là vì isInstanceOfnó kiểm tra thời gian chạy dựa trên thông tin loại có sẵn cho JVM. Và thông tin thời gian chạy đó sẽ không chứa đối số kiểu List(vì xóa kiểu).
Dominique Unruh
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.