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


370

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

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


Scala 2.8 Beta 1 RC4は、型消去の動作にいくつかの変更を加えました。これがあなたの質問に直接影響するかどうかはわかりません。
スコットモリソン

1
それはちょうどどのような種類の消去だ変更されました。それの不足分は、「提案: "Aを持つオブジェクト"の消去は "オブジェクト"ではなく "A"と要約できます実際の仕様はかなり複雑です。とにかく、それはミックスインについてであり、この質問はジェネリックについて懸念しています。
ダニエルC.ソブラル

説明をありがとう-私はscalaの新人です。今はScalaに飛び込むのは悪い時期だと思います。以前は、2.8の変更点を適切なベースから学ぶことができましたが、後で違いを知る必要はありませんでした。
スコットモリソン

1
TypeTagsに関するやや関連のある質問を次に示します。
pvorb 2013

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を使用しています。現在の解決策については、以下の回答をご覧ください。

Javaとは異なり、Java仮想マシン(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が提供するよりも多くの情報が含まれているため、完全なパラメーター化された型をテストできます。

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


3
get方法は次のように定義することができますfor ((om, v) <- _map get key if om <:< m) yield v.asInstanceOf[T]
Aaron Novstrup、2010

4
@Aaron非常に良い提案ですが、Scalaを比較的初めて使う人にとってはコードがわかりにくくなるのではないかと心配しています。私がそのコードを書いたとき、私はScalaをあまり経験していませんでした。それは、この質問/回答に入れる前のいつかでした。
ダニエルC.ソブラル

6
@KimStebel TypeTagパターンマッチングで実際に自動的に使用されることを知っていますか?かっこいい?
ダニエルC.ソブラル2012

1
涼しい!多分あなたは答えにそれを追加する必要があります。
Kim Stebel

1
真上自分の質問に答えるために:はい、コンパイラが生成するManifestのparam自体を、以下を参照してください。stackoverflow.com/a/11495793/694469 「[マニフェスト/タイプタグ]インスタンス[...]は、コンパイラによって暗黙的に作成されています"
KajMagnus

96

これはTypeTagsを使用して行うことができます(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!")
}

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

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


2
[List String @unchecked]このパターンマッチには何も追加されないため、不要なものを削除するとどうなりますか?(使用case strlist if typeOf[A] =:= typeOf[String] =>するだけで、またはcase _ if typeOf[A] =:= typeOf[String] =>バインドされた変数がの本体で必要ない場合でもcase
Nader Ghanbari、2014年

1
私はそれが与えられた例ではうまくいくと思いますが、ほとんどの実際の使用法は要素のタイプを持つことから利益を得ると思います。
tksfz 2014年

上記の例では、ガード条件の前のチェックされていない部分がキャストしませんか?文字列にキャストできない最初のオブジェクトの一致を通過すると、クラスキャスト例外が発生しませんか?
Toby

うーん、ガードを適用する前にキャストがないと思います。チェックされていないビットは、の右側のコード=>が実行されるまでは何もしないものです。(そしてrhsのコードが実行されると、ガードは要素のタイプに静的な保証を提供します。そこにキャストがあるかもしれませんが、それは安全です。)
tksfz

このソリューションはランタイムに大きなオーバーヘッドをもたらしますか?
stanislav.chetvertkov

65

シェイプレス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動作は、スコープ内の所与できるだけ正確WRT消去ようになる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

Scalaの型消去問題を克服する方法があります。「マッチング1での型消去の克服」と「マッチング2での型消去の克服(分散)」では、分散を含む型をラップしてマッチングするためのヘルパーのコーディング方法について説明しています。


これは型の消去を克服しません。彼の例では、val x:Any = List(1,2,3);を実行しています。x match {case IntList(l)=> println(s "Match $ {l(1)}"); case _ => println(s "No match")}は "No match"を生成します
user48956

scala 2.10マクロを見ることができます。
Alex

11

私は、さもなければ素晴らしい言語のこの制限に対する少し良い回避策を見つけました。

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


10
「他の点では素晴らしい言語の制限」は、Scalaの制限ではなく、JVMの制限です。おそらくそれは、JVMで実行されていたとして、Scalaは型情報を含むように設計されている可能性が、私はそのような設計は(すなわち、設計されたとして、あなたは、JavaからScalaのを呼び出すことができます。)は、Javaとの相互運用性を保存しているとは思わない
カール・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
サリバンの要点は正確であり、継承に関連する問題がありますが、これはまだ役に立ちました。
Seth


0

問題を一般化する答えを追加したかった:実行時にリストのタイプの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))

-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(型消去のため)への型引数は含まれません。
Dominique Unruh
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.