ScalaのClassManifestについてハマった(とりあえず解決)
前回の記事の続きです。
ScalaのClassManifestについてハマった(未解決)
http://rabitarochan.hatenablog.com/entry/2012/08/31/234855
この件について、@xuwei_kさんからTwitterのリプライ、gistへのコメント、forkなどをしていただき、色々教えてもらいました。ありがとうございます!!
コンパイルができない理由
コンパイルが出来ない理由については、gistに頂いたコメントのとおりです。
https://gist.github.com/3553602#gistcomment-474588
ClassManifest[A]トレイトに指定する型パラメータと、erasureで戻るJClass[_]クラスに関連がないからです。
# JClassとは、java.lang.Classの別名です。
以下、ClassManifestの該当部分を抜粋して、適当にコメントをしたものです。
// Class を JClass と別名を定義してインポート import java.lang.{ Class => JClass } trait ClassManifest[T] extends OptManifest[T] with Equals with Serializable { /** A class representing the type U to which T would be erased. * Note that there is no subtyping relationship between T and U. */ // JClassの型パラメータが、トレイトの型パラメータ T でないので、関連がない。 def erasure: JClass[_] ... }
コメントに書いた通り、JClassの型パラメータ[_]が、トレイトの型パラメータ[T]でないため、その間に関連がありません。
ですが、前回書いた create メソッドの戻り値に、トレイトの型パラメータと同じ型が戻り値になることを明記しているため、コンパイルが通らなかった、ということです。
// ClassManifest[A] と m.erasure の型パラメータに関連がない。 // そのため、戻り値ClassNameの型パラメータは A だとコンパイラは認識できない。 def create[A]()(implicit m: ClassManifest[A]): ClassName[A] = new ClassName(m.erasure)
たぶん、これで合っていると思います。
では、コンパイルを通すには?
とりあえずコンパイルを通すには、以下2種類の方法がありました。
1. 戻り値を指定しない
戻り値を指定せずにメソッドを定義することで、コンパイルを通すことができます。
ただし、戻り値のデータ型は ClassName[_] になってしまいます。
def create[A]()(implicit m: ClassManifest[A] = new ClassName(m.erasure)
2. m.erasure をキャストする
あまり書きたくないですが、m.erasureに対して asInstanceOf[Class[A]] を呼び出すことで、Class[_]をClass[A]と、型パラメータを指定してClassName[A]のインスタンスを生成できます。
これで戻り値を明記することができます。が、asInstanceOfってあまり使いたくないです。
def create[A]()(implicit m: ClassManifest[A]): ClassName[A] = new ClassName(m.erasure.asInstanceOf[Class[A]])
それ以外の方法は?
現在はまだ見つけてません。
事前定義で erasure を Class[A] で定義し直せばいけるのでは?と思いましたが、全然ダメでした。
# Scalaをまだあまり理解してない・・・。
scala> trait ClassManifestExt[A] extends { def erasure: Class[A] } with ClassManifest[A] <console>:1: error: only type definitions and concrete field definitions allowed in early object initialization section trait ClassManifestExt[A] extends { def erasure: Class[A] } with ClassManifest[A] ^
おわりに
とりあえず、コンパイルを通して想定の値を戻すことは出来ました。
foo(classOf[bar]) と書くのを foo[bar] とかけるかの実験として書いてみましたが、少しずつScalaのことが分かってきた気がします。気がします。
もう少し理解して、もっといい別の答えがあるのであれば、また記事を書きたいと思います。
@rabitarochan まぁ端的に言ってしまえばそうですね。これの解決方法について、「戻り値の型を明示しないというのや、asInstanceOfをしてしまう」よりも、さらによい方法があるかもしれない気がしなくも無いですが
— Kenji Yoshidaさん (@xuwei_k) 9月 1, 2012
いろいろとコメントを下さった@xuwei_kさん、ありがとうございました!!
github にリポジトリ作ってみました。
https://github.com/rabitarochan/scala-classmanifest-test
gist でもよかったのですが、gitの使い方も勉強していきたいので、ソースはなるべく github に上げていこうと思います。
では!