学んだことをなぐり書き


暗黙の型変換

implicit def

所詮はただのメソッドで、それがあるルールの元で省略出来るだけに過ぎない(はず)
/**
 * implicit は「暗黙の」という意味である。
 * implicit def なら「暗黙の関数」という意味になる。
 * scalaはコンパイル時に型チェックが通らなかった時、implicit def が定義されている場合
 * 型チェックを通らなかった値を引数に「暗黙に」呼び出される。
 */
case class A(v: Int)
case class B(var v: Int) {
  def +(b: B) = B(v + b.v)
}

// scala の慣習かは知らないが、「元の型名 to(or 2) 変換後の型名」というメソッド名がよく使われる。
// 2 は to の省略形
implicit def a2b(a: A) = B(a.v)

val a = A(3)
var b = B(5)

// a + b で型エラーが出る。するとコンパイラは implicit メソッドを探しだす(と思う)
// 今回の場合内部的には(多分) val c = a2b(a) + b が走ってる(はず)  
val c = a + b

// これは b = b + a2b(a) + a2b(a) となっている(はず)
b += a + a

println("b:  " + b)
println("c:  " + c)

// implicit def は明示的に呼びだす時もできるので可読性を上げたい時は使う。
var b2 = B(5)
val c2 = a2b(a) + b2

b2 = b2 + a2b(a) + a2b(a)
println("b2: " + b2)
println("c2: " + c2)
b:  B(11)
c:  B(8)
b2: B(11)
c2: B(8)

スコープルール

/**
 * 多分 ruby の private と似たような感じ
 * instance.method の形の instance の部分をレシーバーと呼び、
 * このレシーバーを省略しても呼びだせる状態(scala の場合 this の省略を思い浮かべると分かりやすい)の implicit メソッドなら暗黙の型変換が適応される。
 * このルール(レシーバーを省略できる状態なら暗黙の型変換が適応される)は object import をした時でも適応される。
 */
def hr = println("--------------")
def convert(obj: B) = println(obj)

case class A1(v: String)
case class A2(v: String)
case class A3(v: String)
case class B(v: String)

class C1 {
  implicit def a1ToB(a: A1) = B(a.v)
  protected implicit def a2ToB(a: A2) = B(a.v)
  private implicit def a3ToB(a: A3) = B(a.v)  

  def test1() {    
    convert(A1("eins"))
    convert(A2("zwei"))
    convert(A3("drei"))
  }
}
class C2 extends C1 {
  def test2() {
    // a1ToB と a2ToB は public, protected で見えてるので呼べる
    convert(A1("one"))
    convert(A2("two"))

    // a3ToB は private で見えないので呼べない
    // convert( a3ToB(A3("three")) ) => error: method a3ToB in class C1 cannot be accessed in this.C2
    // convert(A3("three")) => error: type mismatch
  }
}
(new C1).test1()
hr
(new C2).test2()
hr
val obj = new C1

// obj.a1ToB は呼べるがレシーバーなしの形(a1ToB)では呼べないので暗黙の型変換は行われない
convert(obj.a1ToB(A1("hoge")))
// convert(A1("moke")) => error: type mismatch
hr

// object import をしてレシーバーなしの形で呼べるようにしてやれば暗黙の型変換が出来るようになる
import obj._
convert( a1ToB(A1("hoge")) )
convert( A1("moke") )

// 当然 a2ToB は protected なので呼べないし、暗黙の型変換も出来ない
// convert( a2ToB(A2("a")) ) => error: method a2ToB in class C1 cannot be accessed in this.C1 
// convert( A2("a") ) => error: type mismatch
B(eins)
B(zwei)
B(drei)
--------------
B(one)
B(two)
--------------
B(hoge)
--------------
B(hoge)
B(moke)

レシーバーの変換

/**
 * obj が func というメンバーを持っていないにも関わらず、obj.func というコードを書いた場合
 * コンパイラは諦める前に暗黙の型変換を行なう
 */

class A
class B { def hatarake = println("hello work") }

val a = new A

// class A には hatarake メンバーはないので当然エラーになる。
// a.hatarake <= error: value hatarake is not a member of this.A

{
  implicit def a2b(a: A) = new B

  // A を B に暗黙の型変換する implicit def を入れてやれば、変換後に呼ばれる
  a.hatarake
  // 上は多分下のような形に暗黙に変換されている。黒魔術すぎわろた
  (a2b(a)).hatarake
}
hello work
hello work

scala(c) -Xprint:typer

scalac に -Xprint:typer を追加すると全てを省略しない形のソースコードを出力する。
  • test.scala
object O {
  class A
  class B
  implicit def a2b(a: A) = new B
  
  def main(args: Array[String]) {
    val b: B = new A
  }           
}
scalac -Xprint:typer test.scala をすると以下のようなコード出力される。
package <empty> {
  final object O extends java.lang.Object with ScalaObject {
    def this(): object O = {
      O.super.this();
      ()
    };
    class A extends java.lang.Object with ScalaObject {
      def this(): O.A = {
        A.super.this();
        ()
      }
    };
    class B extends java.lang.Object with ScalaObject {
      def this(): O.B = {
        B.super.this();
        ()
      }
    };
    implicit def a2b(a: O.A): O.B = new O.this.B();
    def main(args: Array[String]): Unit = {
      val b: O.B = O.this.a2b(new O.this.A());
      ()
    }
  }
}
val b: O.B = O.this.a2b(new O.this.A());
と暗黙の型変換を全て省略しない形のものを出力された。

scala -Xprint:typer をすると、全てを省略しない形を出力する REPL が起動されます。

暗黙のパラメーター

メンバーのみ編集できます