1

Scala提供了另一种类型界定的手段,与类型界定操作符<:, <%具有微妙的关系。

  • T <=< UT是否与U类型相等

  • T <:< UT是否是U的子类型

  • T <%< UT是否可以隐式地转换为U类型

类型界定常常用于以下两种场景:

  • 在泛型类中,定义在特定条件下的才能使用的方法

  • 协助类型推演

特定方法

T <:< U为例,capital的类型为List[(String, String)],通过调用toMap方法可将其转换为Map[String,String]的类型,这样的转换仅当List的元素类型为Tuple2才合法。

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

capital.toMap  // OK

List的元素类型不是Tuple2时,试图调用toMap是编译失败的。

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

其中,toMap定义在TraversableOnce特质中。

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而立即失败。


horance
255 声望29 粉丝

刘光聪,程序员,敏捷教练,开源软件爱好者,具有多年大型遗留系统的重构经验,对OO,FP,DSL等领域具有浓厚的兴趣。


引用和评论

0 条评论