web-dev-qa-db-ja.com

Scalaでタイプ消去を回避するにはどうすればよいですか?または、コレクションのtypeパラメーターを取得できないのはなぜですか?

Scalaの人生の悲しい事実です。List[Int]をインスタンス化すると、インスタンスがListであることを確認でき、その個々の要素がIntであるが、簡単に確認できるように、List [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?!

なぜですか、どうすれば回避できますか?

365

この回答では、Scala 2.10で廃止されたManifest- APIを使用します。最新のソリューションについては、以下の回答をご覧ください。

Javaとは異なり、Java Virtual Machine(JVM)はジェネリックを取得しなかったため、ScalaはType Erasureで定義されました。これは、実行時にはクラスのみが存在し、その型パラメーターは存在しないことを意味します。この例では、JVMはscala.collection.immutable.Listを処理していることを認識していますが、このリストがIntでパラメーター化されていることは認識していません。

幸いなことに、Scalaにはそれを回避する機能があります。 マニフェストです。マニフェストは、インスタンスが型を表すオブジェクトであるクラスです。これらのインスタンスはオブジェクトであるため、それらを渡したり、保存したり、通常はメソッドを呼び出したりできます。暗黙的なパラメーターのサポートにより、非常に強力なツールになります。たとえば、次の例を見てください。

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

要素を保存するとき、その「マニフェスト」も保存します。マニフェストは、Scala型を表すインスタンスを持つクラスです。これらのオブジェクトにはJVMよりも多くの情報があるため、パラメーター化された完全な型をテストできます。

ただし、Manifestは引き続き進化する​​機能であることに注意してください。その制限の例として、現在は分散について何も知らず、すべてが共変であると想定しています。現在開発中のScalaリフレクションライブラリが完成すると、より安定して安定したものになると思います。

241

TypeTagを使用してこれを行うことができます(Danielが既に言及しているように、私は明示的に説明します)。

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

ClassTagsを使用してこれを行うこともできます(scala-reflectに依存する必要がなくなります)。

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

型パラメーターA自体がジェネリック型であると期待しない限り、ClassTagsを使用できます。

残念ながら、これは少し冗長であり、コンパイラの警告を抑制するには@unchecked注釈が必要です。 TypeTagは、将来コンパイラによってパターンマッチに自動的に組み込まれる可能性があります。 https://issues.scala-lang.org/browse/SI-6517

93
tksfz

shapelessTypeable型クラスを使用して、目的の結果を取得できます。

サンプル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インスタンスが使用可能な場合、可能な限り正確に消去されます。

63
Miles Sabin

使用が制限されている状況で十分な、比較的単純なソリューションを思い付きました。基本的に、matchステートメントで使用できるラッパークラスで型消去の問題が発生するパラメーター化された型をラップします。

case class StringListHolder(list:List[String])

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

これには期待される出力があり、ケースクラスのコンテンツを目的のタイプである文字列リストに制限します。

詳細はこちら: http://www.scalafied.com/?p=6

15
thricejamie

Scalaの型消去の問題を克服する方法があります。 In一致する1のタイプ消去を克服するおよびタイプ消去を克服するMatching 2(Variance)は、いくつかのヘルパーをコーディングして、Varianceなどの型をラップして一致させる方法の説明です。

14
Alex

私は、そうでなければ素晴らしい言語のこの制限に対するわずかに良い回避策を見つけました。

Scalaでは、配列では型消去の問題は発生しません。これを例で示す方が簡単だと思います。

(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)を使用する必要があります。

11
Jus12

Javaは実際の要素タイプを知らないため、List[_]を使用することが最も便利であることがわかりました。その後、警告は消え、コードは現実を説明します-それは未知の何かのリストです。

6
rained_in

これが適切な回避策かどうか疑問に思っています:

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

それはちょっと良くないのですか、ここでポイントを逃していますか?

4
agilesteel

解決策ではありませんが、ラグの下にそれを一掃することなく一緒に生きる方法:@uncheckedアノテーションを追加します。こちらをご覧ください- http://www.scala-lang.org/api/current/index.html#scala.unchecked

1
matanster

問題を一般化する回答を追加したかった:実行時にリストのタイプの文字列表現を取得する方法

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))
0
Steve Robinson