Scala 3 — 书籍

类型类

语言

类型类是一种抽象的参数化类型,它允许您向任何封闭的数据类型添加新行为,而无需使用子类型。如果您来自 Java,您可以将类型类视为类似于 java.util.Comparator[T] 的东西。

Oliveira 等人在 2010 年发表的论文 “类型类作为对象和隐式值” 讨论了 Scala 中类型类的基本思想。尽管该论文使用的是较旧版本的 Scala,但这些思想至今仍然适用。

类型类在多种用例中很有用,例如

  • 表达您不拥有的类型(来自标准库或第三方库)如何符合此类行为
  • 为多种类型表达此类行为,而无需涉及这些类型之间的子类型关系

类型类是具有一个或多个参数的特质,其实现作为 Scala 3 中的 given 实例或 Scala 2 中的 implicit 值提供。

示例

例如,Show 是 Haskell 中一个众所周知的类型类,以下代码展示了在 Scala 中实现它的方法之一。如果你想象 Scala 类没有 toString 方法,你可以定义一个 Show 类型类,为任何希望转换为自定义字符串的类型添加此行为。

类型类

创建类型类的第一步是声明一个参数化特征,它有一个或多个抽象方法。因为 Showable 只有一个名为 show 的方法,所以它写成这样

// a type class
trait Showable[A] {
  def show(a: A): String
}
// a type class
trait Showable[A]:
  extension (a: A) def show: String

请注意,这种方法接近通常的面向对象方法,在该方法中,你通常会按如下方式定义一个特征 Show

// a trait
trait Show {
  def show: String
}
// a trait
trait Show:
  def show: String

有几件重要的事情要指出

  1. Showable 这样的类型类采用类型参数 A 来表示我们为其提供 show 实现的类型;相比之下,像 Show 这样的经典特征没有。
  2. 要为某个类型 A 添加 show 功能,经典特征要求 A extends Show,而对于类型类,我们需要有 Showable[A] 的实现。
  3. 在 Scala 3 中,为了在 Showable 中允许与 Show 相同的方法调用语法,我们将 Showable.show 定义为一个扩展方法。

实现具体实例

下一步是确定应用程序中 Showable 应该为哪些类工作,然后为它们实现该行为。例如,为这个 Person 类实现 Showable

case class Person(firstName: String, lastName: String)

你将定义一个类型为 Showable[Person] 的单一规范值,即类型 PersonShowable 实例,如下面的代码示例所示

implicit val showablePerson: Showable[Person] = new Showable[Person] {
  def show(p: Person): String =
    s"${p.firstName} ${p.lastName}"
}
given Showable[Person] with
  extension (p: Person) def show: String =
    s"${p.firstName} ${p.lastName}"

使用类型类

现在你可以像这样使用这个类型类

val person = Person("John", "Doe")
println(showablePerson.show(person))

请注意,在实践中,类型类通常与类型未知的值一起使用,这与下一节中所示的 Person 类型不同。

val person = Person("John", "Doe")
println(person.show)

同样,如果 Scala 没有可用于每个类的 toString 方法,则可以使用此技术向任何希望转换为 String 的类添加 Showable 行为。

编写使用类型类的函数

与继承一样,你可以定义使用 Showable 作为类型参数的函数

def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit =
  as.foreach(a => println(showable.show(a)))

showAll(List(Person("Jane"), Person("Mary")))
def showAll[A: Showable](as: List[A]): Unit =
  as.foreach(a => println(a.show))

showAll(List(Person("Jane"), Person("Mary")))

具有多个函数的类型类

请注意,如果你想创建一个具有多个函数的类型类,则初始语法如下所示

trait HasLegs[A] {
  def walk(a: A): Unit
  def run(a: A): Unit
}
trait HasLegs[A]:
  extension (a: A)
    def walk(): Unit
    def run(): Unit

一个实际示例

有关在 Scala 3 中如何使用类型类的实际示例,请参阅 多重相等部分 中的 CanEqual 讨论。

此页面的贡献者