判定 - 型 引数 scala




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

この回答は、 Manifest -APIを使用しています。これはScala 2.10では推奨されていません。 現在のソリューションの詳細については、下記の回答をご覧ください。

Javaとは異なり、Java仮想マシン(JVM)はジェネリックを取得しなかったため、Scalaはタイプ消去で定義されました。 つまり、実行時には型パラメータではなくクラスのみが存在します。 この例では、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のリフレクションライブラリが完成すれば、より安定して安定したものになると思います。

List [Int]をインスタンス化すると、インスタンスがListであることを検証でき、その個々の要素がIntであることを確認できますが、リスト[List]ではないことがScalaでの悲しいことです。 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?!

それはなぜですか、どうすれば周りを回ることができますか?


Javaは実際の要素の型を知らないので、 List[_]使うのが最も便利だとわかりました。 その後、警告が消え、コードは現実を記述します。これは不明なもののリストです。


TypeTagsを使ってこれを行うことができます(ダニエルはすでに言及していますが、私は明示しています)。

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

ClassTagsは、型パラメータA自体が汎用型であることを期待しない限り使用できます。

残念ながら少し冗長で、@uncheckedアノテーションがコンパイラ警告を抑制するために必要です。 TypeTagは、今後コンパイラによってパターンマッチに自動的に組み込むことができます: https://issues.scala-lang.org/browse/SI-6517 ://issues.scala-lang.org/browse/SI-6517


あなたはTypeable結果を得るためにshapeless Typeable型クラスを使うことができます。

サンプル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可能なインスタンスを考慮して、可能な限り正確な消去となります。


問題を一般化する答えを追加したかったのです:実行時にリストの型のString表現を取得するにはどうすればいいですか?

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

私は、限られた使用状況で十分である比較的単純な解決策を考え出しました。基本的には、matchステートメントで使用できるラッパークラスの型消去問題の影響を受けるパラメータ化された型をラップします。

case class StringListHolder(list:List[String])

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

これは期待される出力を持ち、ケースクラスの内容を希望のタイプのString Listsに制限します。

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


私は他の素晴らしい言語のこの制限のための少し良い回避策を見つけました。

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 )を使用する必要があることに注意してIntString






type-erasure