隱式函式


隱式轉換發生的時機之一,就是當你嘗試呼叫原物件上所沒有的方法時,編譯器會嘗試從範圍中可用的隱式轉換函式,將原物件包裹轉換為另一個型態,例如在 轉換規則 中看到的例子:
implicit def stringToBuilder(s: String) = new StringBuilder(s)

val old = "oz"
val young = old.insert(1, "r")
println(young) // orz

字串本身並沒有insert()方法,編譯器嘗試尋找可用的隱式轉換函式並套用,最後編譯器會將之變為以下:
implicit def stringToBuilder(s: String) = new StringBuilder(s)

val old = "oz"
val young = stringToBuilder(old).insert(1, "r")
println(young)

再次提醒的是,隱式轉換根據的是來源與目的地型態,而不是隱式函式或變數名稱,而且目的地型態,不是指隱式轉換的傳回值型態,而是指目的物件的 結構型態(Structural typing)。來源型態也可以是型態參數化後的物件,一個例子是scala.Predef 中定義的 any2ArrowAssoc() 函式:
package scala

object Predef {
    class ArrowAssoc[A](x: A) {
       def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
       def →[B](y: B): Tuple2[A, B] = ->(y)
    }
    implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
    ....
}

any2ArrowAssoc()函式來源可以是任何型態,傳回的型態為ArrowAssoc[A],也就是只要隱式轉換的來源是任意型態,而目的型態是具有->與方法的結構型態,any2ArrowAssoc()函式都可以滿足

那麼有any2ArrowAssoc()函式的套用實例嗎?答案就是建立Map時所使用的->,->不是語法的一部份,實際上->是個方法名稱,例如在 Set 與 Map 中看到的一個例子:
val rooms = Map(101 -> "Justin", 102 -> "caterpillar")
println(rooms(101)) // 顯示 Justin
println(rooms(102)) // 顯示 caterpillar

101、102是個Int整數,沒有->方法,編譯器嘗試使用隱式轉換為以下的形式:
val rooms = Map(any2ArrowAssoc(101) -> "Justin", 
any2ArrowAssoc(102) -> "caterpillar")
println(rooms(101)) // 顯示 Justin
println(rooms(102)) // 顯示 caterpillar

所以隱式轉換也是Scala支援擴充語法的一種方式,由於這種特性,使得->看起來就像是內建語法的一部份。

隱式轉換的另一個常見應用,就是轉換來源型態為目的型態,例如:
val v: Long = 10

10是個Int指定給Long變數v,這句陳述看起來很普通,但別忘了,Int、Long、Double等都是AnyVal的子類,彼此之間沒有繼承關係,這樣的轉換如何達成?答案就是透過隱式轉換,在scala.Predef中定義了一堆這類隱式轉換函式:
package scala

object Predef {
    implicit def byte2short(x: Byte): Short = x.toShort
    implicit def byte2int(x: Byte): Int = x.toInt
    implicit def byte2long(x: Byte): Long = x.toLong
    implicit def byte2float(x: Byte): Float = x.toFloat
    implicit def byte2double(x: Byte): Double = x.toDouble

    implicit def short2int(x: Short): Int = x.toInt
    implicit def short2long(x: Short): Long = x.toLong
    implicit def short2float(x: Short): Float = x.toFloat
    implicit def short2double(x: Short): Double = x.toDouble

    implicit def char2int(x: Char): Int = x.toInt
    implicit def char2long(x: Char): Long = x.toLong
    implicit def char2float(x: Char): Float = x.toFloat
    implicit def char2double(x: Char): Double = x.toDouble

    implicit def int2long(x: Int): Long = x.toLong
    implicit def int2float(x: Int): Float = x.toFloat
    implicit def int2double(x: Int): Double = x.toDouble

    implicit def long2float(x: Long): Float = x.toFloat
    implicit def long2double(x: Long): Double = x.toDouble

    implicit def float2double(x: Float): Double = x.toDouble
        ...
}

所以方才的例子,實際上編譯器會轉換為:
val v: Long = int2long(10)