読者です 読者をやめる 読者になる 読者になる

ScalaのClassManifestについてハマった(とりあえず解決)

前回の記事の続きです。

ScalaのClassManifestについてハマった(未解決)
http://rabitarochan.hatenablog.com/entry/2012/08/31/234855

この件について、@さんから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のことが分かってきた気がします。気がします。

もう少し理解して、もっといい別の答えがあるのであれば、また記事を書きたいと思います。

いろいろとコメントを下さった@さん、ありがとうございました!!

github にリポジトリ作ってみました。
https://github.com/rabitarochan/scala-classmanifest-test

gist でもよかったのですが、gitの使い方も勉強していきたいので、ソースはなるべく github に上げていこうと思います。

では!