あるプログラマの日記

プログラマのメモ、出来事、考えたこと、勉強とかの雑記

暗黙の型変換 (implicit conversion)

型変換用の関数の定義に implicit つけると型変換が必要な場面で自動的に型変換関数が実行される。

例えば forへ指定する Int to Int で暗黙の型変換が使用されている。
1 to 3 は RichInt の to メソッドから Range(1,2,3) が返される。
1 の Int は、暗黙の型変換で Int から RichInt に変換されて to メソッドが呼び出される。

scala> val foo = 1 to 3
foo: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)

to メソッド はscala.runtime.RichInt で定義されている。

final class RichInt(val self: Int) extends ScalaNumberProxy[Int] with RangedProxy[Int] {
..snip..
  def to(end: Int): Range.Inclusive = Range.inclusive(self, end)
..snip..
}

Int から RichInt への暗黙の型変換関数 intWrapper は、scala.Predef へ extends している scala.LowPriorityImplicits で定義されている。
Int に定義がないメソッドが呼ばれた場合は RichInt で定義しているメソッドを探して見つかった時に RichInt へ変換して該当するメソッドが呼ばれる。

object Predef extends LowPriorityImplicits {
..snip..
}

LowPriorityImplicits には Int 以外にも Byte や Short 等から Rich.. 型への暗黙の型変換関数が用意されている。

class LowPriorityImplicits {
..snip..
  implicit def byteWrapper(x: Byte)       = new runtime.RichByte(x)
  implicit def shortWrapper(x: Short)     = new runtime.RichShort(x)
  implicit def intWrapper(x: Int)         = new runtime.RichInt(x)
  implicit def charWrapper(c: Char)       = new runtime.RichChar(c)
  implicit def longWrapper(x: Long)       = new runtime.RichLong(x)
  implicit def floatWrapper(x: Float)     = new runtime.RichFloat(x)
  implicit def doubleWrapper(x: Double)   = new runtime.RichDouble(x)  
  implicit def booleanWrapper(x: Boolean) = new runtime.RichBoolean(x)
..snip..
}

同じ種類の型変換の暗黙の関数定義があると暗黙の型変換を自動的に行う際にエラーになる。

scala> implicit def intWrap(a: Int) = new runtime.RichInt(a)
intWrap: (a: Int)scala.runtime.RichInt

scala> 1 to 5
<console>:9: error: type mismatch;
 found   : Int(1)
 required: ?{val to(x$1: ?>: Int(5) <: Any): ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method intWrapper in class LowPriorityImplicits of type (x: Int)scala.runt
ime.RichInt
 and method intWrap in object $iw of type (a: Int)scala.runtime.RichInt
 are possible conversion functions from Int(1) to ?{val to(x$1: ?>: Int(5) <: An
y): ?}
              1 to 5
              ^

まとめ

  • 暗黙の型変換用の関数は「implicit」をつけて定義する。
  • 使われる変換関数は自動的に最適な型が選択され変換される。
  • 同じ種類の型変換関数が複数ある場合はエラーになる。
  • 型変換対照を探すのは同じスコープ内に限定される。
  • 暗黙の型変換を使うとそのクラスに無いメソッドを呼び出せるようになって便利な反面、乱用するとわけがわからなくなりそう。
    • 暗黙の型変換関数を定義するクラス、オブジェクトを限定してあらかじめ決めておく *1
    • スーパークラスからサブクラスへの暗黙の型変換関数は不要

*1:どこでどう型変換されているのかがわからなくなるため