Essential Scala: Type Constraints

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 => ToFunction1[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,保证AB的下界。

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方法。

你可能感兴趣的:(Essential Scala: Type Constraints)