trait からフィールドの型情報を取り出すためのライブラリを作った

( [Scala Advent Calendar 2014](http://qiita.com/advent-calendar/2014/scala) の 11 日目です )

# 動機

* trait で表現されたデータ構造から JSON や XML へ変換するコードを自動生成したい
  * つまり trait からフィールドの名前とその型情報だけ抜き出したい
* しかしマクロやリフレクションに依存したライブラリは書きたくない
  * 将来いつ変わるか分からない！
     * 歴史的には Manifest から TypeTag への変更といった実例あり
  * 変わったときのライブラリの書き直しはしんどい
     * だとしたら型情報だけ取り出してくれる中間層を用意すればいいのでは
     * その中間層が Scala の内部実装を隠蔽してくれればいい！

# 実例

たとえば下記のようなデータ構造を表現した trait に対して

```InspectorSample.scala
package example

trait SampleStructure {
  def x: Int
  def y: SampleGeneric[String, Long, Int]
  def z: List[Int]
}
trait SampleGeneric [A, B, C]{
  def a: A
  def b: B
  def c: Nested[Nested[Nested[C]]]
}
trait Nested[A]{
  def foo: A
}
```

以下のように取り出せるようになります。

```scala
  val digest = TypeReflector.inspect[SampleStructure]

  println(digest.members.find(_.decodedName == "z").map(_.resultType.typedName))
  // Some(scala.collection.immutable.List[scala.Int])

  val name = for {
    y <- digest.members.find(_.decodedName == "y")
    b <- y.resultType.members.find(_.decodedName == "b")
  } yield {
    b.resultType.typedName
  }
  println(name)
  // Some(scala.Long)
```

コンパイル時に消されているはずの型パラメータも問題なく取得できています(!)

次は階層を再帰的に走査する関数 `dump` の例です。

```InspectoSample.scala
object InspectorSample extends App {
  val digest = TypeReflector.inspect[SampleStructure]
  println(dump(digest))

  def dump(digest: TypeDigest, indent: Int = 1): String = {
    val lines = digest.typedName +: digest.members.
      map { f => s"${f.decodedName} : ${dump(f.resultType, indent + 1)}" }.
      map { "  " * indent + _ }

    lines.mkString("\n")
  }
}
```

実行してみます。

```
> taupe-app/run
[info] Running example.InspectorSample 
example.SampleStructure
  z : scala.collection.immutable.List[scala.Int]
  y : example.SampleGeneric[java.lang.String,scala.Long,scala.Int]
    c : example.Nested[example.Nested[example.Nested[scala.Int]]]
      foo : example.Nested[example.Nested[scala.Int]]
        foo : example.Nested[scala.Int]
          foo : scala.Int
    b : scala.Long
    a : java.lang.String
  x : scala.Int
```

この出力により、たとえば `example.SampleStructure` 型のオブジェクト `obj` に対して
`obj.y.c.foo.foo.foo` は `Int` 型であるということが分かります。

念のため試してみましょう。

```
> ~taupe-app/console
[info] Starting scala interpreter...

scala> def obj: example.SampleStructure = ???
obj: example.SampleStructure

scala> def f = obj.y.c.foo
f: example.Nested[example.Nested[Int]]

scala> def f = obj.y.c.foo.foo
f: example.Nested[Int]

scala> def f = obj.y.c.foo.foo.foo
f: Int
```

カンペキです。

# 利用方法

* ライブラリのリポジトリ : [Salad](https://github.com/x7c1/Salad)
* サンプルのプロジェクト : [sample-taupe](https://github.com/x7c1/Cliche/tree/master/scala/sample-taupe)

sbt からは [こんな形のビルド定義](https://github.com/x7c1/Cliche/blob/26ba7932b5add787b4ac75d0d3aaa7f5b1cf30e7/scala/sample-taupe/project/TaupeBuild.scala#L17-L19) で取り込むことができます。

```scala
    dependsOn(ProjectRef(uri("git://github.com/x7c1/Salad.git#0.1"), "salad-lib"))
```

# おまけ

用意されているマクロ `TypeExpander` からも

```scala
  val digest = TypeExpander.inspect[SampleStructure]
  println(dump(digest))
```

同一の結果が得られます。

```
example.SampleStructure
  z : scala.collection.immutable.List[scala.Int]
  y : example.SampleGeneric[java.lang.String,scala.Long,scala.Int]
    c : example.Nested[example.Nested[example.Nested[scala.Int]]]
      foo : example.Nested[example.Nested[scala.Int]]
        foo : example.Nested[scala.Int]
          foo : scala.Int
    b : scala.Long
    a : java.lang.String
  x : scala.Int
```

これは下記のように使い分けることを目的としたものです。

* 実行時速度が必要ならマクロ
* コンパイル速度が必要ならリフレクション

どちらも同じ `TypeDigest` 型なので、これを利用している限り、
どちらに依存していても任意に切り替えることができます。

# 以上

快適な静的型付けライフを！