Scala Essentials: 類型約束

Scala提供了另外一種類型界定的手段,與類型界定操做符<:, <%具備微妙的關係。api

  • T <=< UT是否與U類型相等app

  • T <:< UT是不是U的子類型ide

  • T <%< UT是否能夠隱式地轉換爲U類型函數

類型界定經常用於如下兩種場景:性能

  • 在泛型類中,定義在特定條件下的才能使用的方法ui

  • 協助類型推演this

特定方法

T <:< U爲例,capital的類型爲List[(String, String)],經過調用toMap方法可將其轉換爲Map[String,String]的類型,這樣的轉換僅當List的元素類型爲Tuple2才合法。scala

val capital = List(
  "US" -> "Washington", 
  "France" -> "Paris"
  "Japan" -> "Tokyo")

capital.toMap  // OK

List的元素類型不是Tuple2時,試圖調用toMap是編譯失敗的。code

val phones = List("+9519728872", "+9599820012")
phones.toMap  // error: Cannot prove that String <:< (T, U).

其中,toMap定義在TraversableOnce特質中。orm

trait TraversableOnce[+A] {
  def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] = {
    val b = immutable.Map.newBuilder[T, U]
    for (x <- self)
      b += x
    b.result
  }
}

其中「隱私參數」implicit ev: A <:< (T, U)的表示中,<::<其本質是一個泛型類,其定義在scala.Predef中。更有甚者,A <:< (T, U)實際上是<:<[A, (T, U)]的中綴表示。其中,<::<是一個具備兩個類型參數的泛型類。

@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
sealed abstract class <:<[-From, +To] extends (From => To)

From => To實際上是Function1[From, To]特質的一個語法糖。也就是說,<:<其本質是一個一元函數。接下來的一個疑問是:對於implicit ev: A <:< (T, U),編譯器如何找到對應的「隱式值」呢?

事實上「隱式值」默認由Predef.conforms的工廠方法提供,它是一個無參的隱式轉換函數。

implicit def conforms[A]: A <:< A = new <:<[A,A] { 
  override def apply(a: A): A = a
}
val capital = List(
  "US" -> "Washington", 
  "France" -> "Paris"
  "Japan" -> "Tokyo")

capital.toMap()  // OK

對於此例,conforms[(String, String)]生成<:<[(String, String), (String, String)]類型的隱式值。

標準庫爲了改善性能,避免每次調用toMap時都new一個<:<[A,A]類型的實例,引入了享元模式。

private[this] final val singleton_<:< = new <:<[Any,Any] { 
  def apply(x: Any): Any = x 
}

implicit def conforms[A]: A <:< A = {
  singleton_<:<.asInstanceOf[A <:< A]
}

<:<:<

def foo[A](i:A)(implicit ev : A <:< Serializable) = i
foo(1)     // error
foo("hi")  // ok

def bar[A <: Serializable](i:A) = i
bar(1)     // compile error
bar("hi")  // ok

<:<:<之間到底有什麼區別呢?<:是一個類型限定操做符,編譯器保證其子類型的約束關係;而<:<是一個類型,編譯器證實其子類型的約束關係。二者在使用場景,類型推演等方面存在微妙的差別。

def foo[A, B <: A](a: A, b: B) = (a,b)
foo(1, List(1,2,3))   // (Any, List[Int]) = (1,List(1, 2, 3))

傳入第一個參數是Int類型,第二個參數是List[Int],顯然這不符合B <: A的約束。爲了知足這個約束,編譯器會繼續向上尋找最近的公共父類型來匹配,因而在第一個參數進一步推演爲Any,使得List[Int]剛好符合Any的子類型。

def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a, b)
bar(1, List(1,2,3))  // error: Cannot prove that List[Int] <:< Int.

<:<則更加嚴格,對於本例由於類型推演優先於隱式解析,第一個參數是Int類型,第二個參數是List[Int],編譯器試圖證實List[Int] <:< Int而當即失敗。

相關文章
相關標籤/搜索