Scala에서 유형 삭제를 어떻게 해결합니까? 또는 왜 내 컬렉션의 type 매개 변수를 얻을 수 없습니까?


370

스칼라에서 슬픈 사실은 List [Int]를 인스턴스화하면 인스턴스가 List인지 확인할 수 있고, 인스턴스의 개별 요소가 Int인지 확인할 수 있다는 것입니다. Int]로 쉽게 확인할 수 있습니다.

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?!

-unchecked 옵션은 유형 삭제에 대해 책임을지게합니다.

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?!

그 이유는 무엇이며 어떻게 해결해야합니까?


Scala 2.8 Beta 1 RC4는 유형 삭제 작동 방식을 일부 변경했습니다. 이것이 귀하의 질문에 직접 영향을 미치는지 확실하지 않습니다.
Scott Morrison

1
그건 그냥 어떤 종류의 삭제의 변경되었습니다. " 제안 :"Object with A "의 삭제는"Object "대신"A " 로 요약 할 수 있습니다 . 실제 사양은 다소 복잡합니다. 어쨌든 믹스 인에 관한 것이며,이 질문은 제네릭에 관한 것입니다.
Daniel C. Sobral

설명해 주셔서 감사합니다. 저는 스칼라 초보자입니다. 나는 지금 스칼라에 뛰어 들기에 나쁜 시간 인 것 같습니다. 이전에는 좋은 기반에서 2.8의 변경 사항을 배울 수 있었지만 나중에는 그 차이를 알 필요가 없었습니다!
Scott Morrison

1
여기에 대한 다소 관련 질문 TypeTag .
pvorb

2
을 (를) 실행하는 scala 2.10.2대신이 경고가 표시되었습니다. <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?") ^귀하의 질문과 답변이 매우 도움이된다고 생각하지만이 업데이트 된 경고가 독자에게 유용한 지 확실하지 않습니다.
케빈 메러디스

답변:


243

이 답변은 ManifestScala 2.10부터 더 이상 사용되지 않는 -API를 사용합니다 . 최신 솔루션에 대해서는 아래 답변을 참조하십시오.

Scala는 Java와 달리 JVM (Java Virtual Machine)이 제네릭을 얻지 못했기 때문에 Type Erasure로 정의되었습니다. 이는 런타임시 유형 매개 변수가 아닌 클래스 만 존재 함을 의미합니다. 이 예에서 JVM은가 처리하고 있음을 알고 scala.collection.immutable.List있지만이 목록이로 매개 변수화되어 있지는 않습니다 Int.

다행히도 스칼라에는이를 해결할 수있는 기능이 있습니다. 그것은이다 매니페스트 . 매니페스트는 인스턴스가 유형을 나타내는 객체 인 클래스입니다. 이러한 인스턴스는 객체이므로이를 전달하고 저장하며 일반적으로 메소드를 호출 할 수 있습니다. 암시 적 매개 변수를 지원하므로 매우 강력한 도구가됩니다. 예를 들어 다음 예를 보자.

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

요소를 저장할 때 "Manifest"도 저장합니다. 매니페스트는 인스턴스가 스칼라 유형을 나타내는 클래스입니다. 이러한 객체에는 JVM보다 많은 정보가 있으므로 매개 변수화 된 전체 유형을 테스트 할 수 있습니다.

그러나 a Manifest는 여전히 진화하는 기능입니다. 한계의 예로서, 현재 분산에 대해 아무것도 모르고 모든 것이 공변량이라고 가정합니다. 현재 개발중인 Scala 리플렉션 라이브러리가 완성되면 더 안정적이고 견고해질 것으로 기대합니다.


3
get방법으로 정의 될 수있다 for ((om, v) <- _map get key if om <:< m) yield v.asInstanceOf[T].
Aaron Novstrup

4
@Aaron 아주 좋은 제안이지만, 스칼라를 처음 접하는 사람들을위한 코드가 모호해질 수 있습니다. 나는 그 코드를 작성할 때 스칼라에 대한 경험이 많지 않았습니다.
Daniel C. Sobral

6
@KimStebel TypeTag패턴 일치에 실제로 자동으로 사용되는 것을 알고 있습니까? 쿨?
Daniel C. Sobral

1
멋있는! 아마도 대답에 추가해야 할 것입니다.
Kim Stebel

1
바로 위의 내 자신의 질문에 대답하려면 예, 컴파일러가 Manifest매개 변수 자체를 생성합니다 . stackoverflow.com/a/11495793/694469 "[manifest / type-tag] 인스턴스 [...]가 컴파일러에 의해 암시 적으로 생성됩니다 "
KajMagnus

96

다니엘이 이미 언급했듯이 TypeTag를 사용 하여이 작업을 수행 할 수 있지만 명시 적으로 철자하겠습니다.

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

ClassTag를 사용 하여이 작업을 수행 할 수도 있습니다 (스칼라 반사에 의존하지 않아도 됨).

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

type 매개 변수 A자체가 제네릭 형식 이 아닌 경우 ClassTag를 사용할 수 있습니다 .

불행히도 조금 장황하며 컴파일러 경고를 표시하지 않으려면 @unchecked 주석이 필요합니다. https://issues.scala-lang.org/browse/SI-6517 : 나중에 TypeTag가 컴파일러에서 자동으로 패턴 일치에 통합 될 수 있습니다


2
[List String @unchecked]이 패턴 일치에 아무것도 추가하지 않으므로 불필요한 제거는 어떻습니까 (사용 case strlist if typeOf[A] =:= typeOf[String] =>하면 또는 case _ if typeOf[A] =:= typeOf[String] =>바인딩 된 변수가 본문에 필요하지 않은 경우 에도 case).
Nader Ghanbari

1
나는 그것이 주어진 예에서 효과가 있다고 생각하지만 대부분의 실제 사용법은 요소 유형을 갖는 것이 도움이 될 것이라고 생각합니다.
tksfz

위의 예에서 가드 상태 앞에있는 검사되지 않은 부분이 캐스트되지 않습니까? 문자열로 캐스트 할 수없는 첫 번째 객체에서 일치를 수행 할 때 클래스 캐스트 예외가 발생하지 않습니까?
Toby

흠 아니 가드를 적용하기 전에 캐스트가 없다고 생각합니다. 검사되지 않은 비트는 오른쪽의 코드 =>가 실행될 때까지 일종의 작동하지 않습니다 . (그리고 rhs의 코드가 실행될 때, 경비원은 요소의 유형에 대한 정적 보증을 제공합니다. 캐스트가있을 수 있지만 안전합니다.)
tksfz

이 솔루션은 상당한 런타임 오버 헤드를 생성합니까?
stanislav.chetvertkov

65

shapeless 에서 Typeabletype 클래스를 사용하여 원하는 결과를 얻을 수 있습니다.

샘플 REPL 세션

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

사용 가능한 cast범위 내 Typeable인스턴스를 고려할 때 가능한 한 정밀한 삭제 작업 이 수행됩니다 .


14
"캐스트"오퍼레이션은 전체 콜렉션과 서브 컬렉션을 반복적으로 수행하고 모든 관련 값이 올바른 유형인지 확인합니다. (즉, l1.cast[List[String]]대략 for (x<-l1) assert(x.isInstanceOf[String]) 데이터 구조가 크거나 캐스트가 매우 자주 발생하는 경우 이는 허용되지 않는 오버 헤드 일 수 있습니다.
Dominique Unruh

16

필자는 제한된 사용 상황에서 충분할 비교적 간단한 솔루션을 고안했습니다. 기본적으로 match 문에 사용할 수있는 래퍼 클래스의 유형 삭제 문제로 인해 발생하는 매개 변수화 된 유형을 래핑했습니다.

case class StringListHolder(list:List[String])

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

예상 출력이 있으며 케이스 클래스의 컨텐츠를 원하는 유형 인 문자열 목록으로 제한합니다.

자세한 내용은 여기 : http://www.scalafied.com/?p=60


14

스칼라에는 유형 삭제 문제를 극복 할 수있는 방법이 있습니다. 에서 일치 하나의 극복 유형 소거일치의 극복 유형 소거 2 (분산)는 코드에 몇 가지 헬퍼 일치를 위해, 분산을 포함하여 유형을 포장하는 방법에 대한 몇 가지 설명이있다.


이것은 유형 삭제를 극복하지 못합니다. 그의 예에서, val x : Any = List (1,2,3); x 일치 {case IntList (l) => println (s "Match $ {l (1)}"); case _ => println (s "No match")}는 "No match"를 생성합니다.
user48956

scala 2.10 매크로를 살펴볼 수 있습니다.
Alex

11

나는 그렇지 않은 멋진 언어 의이 제한에 대해 약간 더 나은 해결 방법을 찾았습니다.

스칼라에서는 삭제 유형 문제가 배열에서 발생하지 않습니다. 예를 들어 이것을 설명하는 것이 더 쉽다고 생각합니다.

의 목록이 있다고 가정 (Int, String)하면 다음은 유형 삭제 경고를 제공합니다.

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

이를 해결하려면 먼저 케이스 클래스를 작성하십시오.

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

그런 다음 패턴 일치에서 다음과 같은 작업을 수행하십시오.

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

완벽하게 작동하는 것 같습니다.

목록 대신 배열을 사용하려면 코드를 약간 변경해야하지만 큰 문제는 아닙니다.

를 사용 case a:Array[(Int, String)]하면 여전히 유형 삭제 경고가 표시되므로 새 컨테이너 클래스 (이 예에서는 IntString) 를 사용해야합니다 .


10
"그렇지 않은 멋진 언어의 제한"은 스칼라의 제한이 아니라 JVM의 제한입니다. 아마도 Scala는 JVM에서 실행되는 유형 정보를 포함하도록 설계되었을 수도 있지만, 이와 같은 설계가 Java와의 상호 운용성을 유지하지 못할 것이라고 생각합니다 (예 : 설계 한대로 Java에서 Scala를 호출 할 수 있음)
Carl G

1
후속 조치로, .NET / CLR에서 Scala에 대한 정규화 된 제네릭에 대한 지원 은 지속적인 가능성입니다.
Carl G

6

Java는 실제 요소 유형을 알지 못하기 때문에을 사용하는 것이 가장 유용하다는 것을 알았습니다 List[_]. 그런 다음 경고가 사라지고 코드는 현실을 설명합니다-알 수없는 것의 목록입니다.


4

이것이 적절한 해결 방법인지 궁금합니다.

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

"빈 목록"경우와 일치하지 않지만 경고가 아닌 컴파일 오류가 발생합니다!

error: type mismatch;
found:     String
requirerd: Int

반면에 이것은 작동하는 것 같습니다 ....

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

좀 더 나아지지 않습니까? 아니면 여기서 요점을 놓치고 있습니까?


3
List [Any] 유형 인 List (1, "a", "b")와 작동하지 않습니다.
sullivan-

1
설리반의 요점은 정확하고 상속과 관련된 문제가 있지만 여전히 유용하다고 생각했습니다.
세스


0

나는 문제를 일반화하는 답변을 추가하고 싶었다 : 런타임시 내 목록 유형의 문자열 표현을 얻는 방법

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

패턴 매치 가드 사용

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

4
이것이 작동하지 않는 이유 isInstanceOf는 JVM에 사용 가능한 유형 정보를 기반으로 런타임 검사를 수행하기 때문입니다. 그리고 해당 런타임 정보에는 List유형 삭제 때문에 유형 인수가 포함되지 않습니다 .
도미니크 Unruh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.