Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

ScalaのAdvanced Exampleを写経する(12)-Views

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.
* In a selection e.m with e of type T, if the selector m does not denote a member of T.

A Tour of Scala: Views | The Scala Programming Language

要はその型に該当するものがなかったらコンバージョンするよってことだね。

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

みたいな。
この辺が原因かなあ?