Implicit Conversionと関連する話。
vectors.scala | The Scala Programming Language
Views are applied in two situations:
* If an expression e is of type T, and T does not conform to the expression's expected type pt.
A Tour of Scala: Views | The Scala Programming Language
* In a selection e.m with e of type T, if the selector m does not denote a member of T.
要はその型に該当するものがなかったらコンバージョンするよってことだね。
package sample.snippet object vectors { trait Vector[N] { type Self <: Vector[N] def *(x: N): Self def +(x: N): Self def /(x: N): Self def -(x: N): Self def apply(i: Int): N def length: Int override def toString(): String = { val sb = new StringBuilder(getClass.getName) sb.append('(') var i = 0 while (i < length) { if (i > 0) sb.append(',') sb.append(apply(i).toString) i += 1 } sb.append(')') sb.toString } } final class Raised[N](n: N) { def *(x: Vector[N]): Vector[N] = x * n def +(x: Vector[N]): Vector[N] = x + n def /(x: Vector[N]): Vector[N] = x / n def -(x: Vector[N]): Vector[N] = x - n } implicit def raiseToVector[N](n: N): Raised[N] = new Raised[N](n) case class DoubleVector(v: Array[Double]) extends Vector[Double] { type Self = DoubleVector private def rep(f: Double => Double): Self = { val nv = new Array[Double](v.length) var i = 0; while(i < nv.length) { nv(i) = f(v(i)) i += 1 } DoubleVector(nv) } def *(x: Double) = rep(k => k * x) def +(x: Double) = rep(k => k + x) def /(x: Double) = rep(k => k / x) def -(x: Double) = rep(k => k - x) def apply(i: Int) = v(i) def length = v.length } implicit def doubleArrayToVector(arr: Array[Double]) = new DoubleVector(arr) def DoubleVector(d: Double*) = new DoubleVector(d.toArray) def main(args: Array[String]) { val v = DoubleVector(1, 2, 3, 4, 5, 6) println(v) println(v * 4.0) println(4.0 + v) } }
type Self <: Vector[N]
Vector実装クラスでSelfにVectorの実装クラスを代入する。しないとコンパイルエラーになる。「
println(v * 4.0)
はDoubleVector#*が呼び出されるので、すべての要素に4.0を掛けることになる。
でも
println(4.0 + v)
は全然別で、Double型に引数Vector型を取る+メソッドはない。
だからコンバージョンする。
implicit def raiseToVector[N](n: N): Raised[N] = new Raised[N](n)
これでDoubleからRaisedにコンバージョンされる。
で、Raisedクラスの
def +(x: Vector[N]): Vector[N] = x + n
を呼び出す。
ここで注意するポイントは「x + n」となってるところ。
レシーバと引数を入れ替えてる。
ここではxがDoubleVectorで、nはRaisedのコンストラクタ引数つまりDouble。
DoubleVectorの+メソッドで引数がDouble4.0の演算になる。
結果としてすべての要素に4.0を足す処理になる。
実行結果。
sample.snippet.vectors$DoubleVector(1.0,2.0,3.0,4.0,5.0,6.0) sample.snippet.vectors$DoubleVector(4.0,8.0,12.0,16.0,20.0,24.0) sample.snippet.vectors$DoubleVector(5.0,6.0,7.0,8.0,9.0,10.0)
ちなみに
val sb = new StringBuilder(getClass.getName)
が、元のソースでは
val sb = new StringBuilder(getClass().getSimpleName())
になってるんだけど、getSimpleNameを使うと実行時例外になる。
Exception in thread "main" java.lang.InternalError: Malformed class name at java.lang.Class.getSimpleName(Class.java:1129) at sample.snippet.vectors$Vector$class.toString(vectors.scala:16) at sample.snippet.vectors$DoubleVector.toString(vectors.scala:38) at java.lang.String.valueOf(String.java:2615) at java.io.PrintStream.print(PrintStream.java:616) at java.io.PrintStream.println(PrintStream.java:753) at scala.Console$.println(Console.scala:162) at scala.Predef$.println(Predef.scala:157) at sample.snippet.vectors$.main(vectors.scala:65) at sample.snippet.vectors.main(vectors.scala)
ClassクラスのgetSimplyNameはこうなってる。
if (length < 1 || simpleName.charAt(0) != '$') throw new InternalError("Malformed class name");
Scalaはコンパイルしてクラスファイルを作ると、本来Javaでは使えない「$」をつけたクラスファイルを作る。
vectors$DoubleVector$$anonfun$$div$1.class vectors$DoubleVector$$anonfun$$minus$1.class vectors$DoubleVector$$anonfun$$plus$1.class vectors$DoubleVector$$anonfun$$times$1.class vectors$DoubleVector$.class vectors$DoubleVector.class
みたいな。
この辺が原因かなあ?