Scala
提供了3
个类型约束的类:
-
T =:= U
:T
是否等于U
-
T <:< U
:T
是否为U
的子类型 -
T <%< U
:T
是否可隐式转换为U
它们对于协助类型推演,及其设计Rich Interface
具有重要意义。
类型推演
类型推演失败
def firstLast[A, C <: Iterable[A]](c: C) = (c.head, c.last)
假如firstLast
被实例化为:
firstLast(List("first", "second", "...", "last"))
期待其类型参数推演为:
def firstLast[String, List[String] <: Iterable[String]](c: List[String]) =
(c.head, c.last)
一切似乎合情合理,符合常人的思维习惯,不幸地是,编译器报错了。
error: inferred type arguments [Nothing,List[String]] do not conform to method firstLast's type parameter bounds [A,C <: Iterable[A]]
因为编译器无法同时推演[A, C]
两个类型参数,这需要借助「柯里化」改善类型推演的能力。
柯里化与类型约束
def firstLast[A, C](c: C)(implicit ev: <:<[C, Iterable[A]]) =
(c.head, c.last)
同样地,假如firstLast
被实例化为:
firstLast(List("first", "second", "...", "last"))
则firstLast
类型参数推演为:
def firstLast[String, List[String]](c: List[String])
(implicit ev: <:<[List[String], Iterable[String]]) =
(c.head, c.last)
其中,ev
的隐式值编译器为其找到了Predef.conforms[List[String]]
,它生成了一个<:<[List[String], List[String]]
类型的实例。
sealed abstract class <:<[-From, +To] extends (From => To)
implicit def conforms[A]: <:<[A, A] = new <:<[A, A] {
def apply(a: A): A = a
}
因为<:<[-From, +To]
的From
为逆变,To
为协变,为此<:<[List[String], List[String]]
是<:<[List[String], Iterable[String]]
的子类型,满足firstLast
的类型规则。
中缀表达式
具有两个「类型参数」的泛型类,可以表示为「中缀表达式」。例如Map[K, V]
,可以表示为K Map V
。
但对于K Map V
的表示法可读性太差。特殊地,如果类名为操作符,例如<:<[A, B]
类,中缀表达式为A <:< B
,表现力得到了提升。
因此,<:<
类可以实现为:
sealed abstract class <:<[-From, +To] extends (From => To)
implicit def conforms[A]: A <:< A = new A <:< A {
def apply(a: A): A = a
}
而firstLast
可实现为:
def firstLast[A, C](c: C)(implicit ev: C <:< Iterable[A]) =
(c.head, c.last)
一元函数
事实上,From => To
是Function1[From, To]
的语法糖;其中,Function1
表示一元函数的特质。
trait Function1[-T, +R] {
def apply(t: T): R
}
因此,<:<
实际为Function1
的子类,因为没有重写apply
,因此它是一个抽象类。
sealed abstract class <:<[-From, +To] extends Function1[From, To]
implicit def conforms[A]: A <:< A = new A <:< A {
def apply(a: A): A = a
}
@implicitNotFound
当firstLast
的类型推演错误时,@implicitNotFound
能给出更有意义的错误提示,而不是提示找不到相关隐式值的无用信息。
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
sealed abstract class <:<[-From, +To] extends Function1[From, To]
特定方法
类型约束除了能够改善类型推演的能力之外,还能够支持在特殊场景下才能使用的方法,常常称为「特定方法」。这是Scala
定义Rich Interface
重要的设计技术之一。
Option.orNull
Map.get
返回Option
类型,当查找成功时返回Some
,否则返回None
。
val capitals = Map("China" -> "Beijing", "USA" -> "Washington")
val capital = capitals.get("China").orNull
其中Option.orNull
仅对AnyRef
的子类有效,而对于AnyVal
是非法的。因此,Option[A]
可以实例化为Option[Int]
,只需要不调用orNull
即可,否则立即发生编译时错误。
可以使用<:<[A, B]
对Option.orNull
进行类型约束,定义特定方法;其中,「特定方法」是设计Rich Interface
的利器。
sealed abstract class Option[+A] {
def orNull[B >: A](implicit ev: Null <:< B): B =
this getOrElse ev(null)
}
协变
Option[+A]
是协变的;也就是说,如果A <: B
,那么Option[A] <: Option[B]
。但orNull
如果如此定义,将使得<:<[Null, A]
中的A
成为逆变点,从而导致编译失败。
sealed abstract class Option[+A] {
// A为逆变点,与Option[+A]矛盾,编译失败
def orNull(implicit ev: <:<[Null, A]): A =
this getOrElse ev(null)
}
sealed abstract class <:<[-From, +To] extends Function1[From, To]
当
<:<[-From, +To]
作为函数参数时,型变是反转过来的,即<:<[+From, -To]
。
解决办法就是增加不变参数B
,并且B >: A
,保证A
为B
的下界。
sealed abstract class Option[+A] {
def orNull[B >: A](implicit ev: Null <:< B): B = this getOrElse ev(null)
}
隐式值
val capital = capitals.get("China").orNull
Option.orNull
的类型参数推演为:
sealed abstract class Option[String] {
def orNull[String >: String](implicit ev: Null <:< String): String =
this getOrElse ev(null)
}
同样地,ev
的隐式值为Predef.conforms[Null]
,即<:<[Null, Null]
类型的实例。其中,<:<[Null, Null]
为<:<[Null, String]
子类,满足推演规则。
另外,ev(null)
返回的值为null
,它调用的是Predef.conforms[Null].apply
方法。