类型类是一种抽象的参数化类型,它允许您向任何封闭的数据类型添加新行为,而无需使用子类型。如果您来自 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
有几件重要的事情要指出
- 像
Showable
这样的类型类采用类型参数A
来表示我们为其提供show
实现的类型;相比之下,像Show
这样的经典特征没有。 - 要为某个类型
A
添加 show 功能,经典特征要求A extends Show
,而对于类型类,我们需要有Showable[A]
的实现。 - 在 Scala 3 中,为了在
Showable
中允许与Show
相同的方法调用语法,我们将Showable.show
定义为一个扩展方法。
实现具体实例
下一步是确定应用程序中 Showable
应该为哪些类工作,然后为它们实现该行为。例如,为这个 Person
类实现 Showable
case class Person(firstName: String, lastName: String)
你将定义一个类型为 Showable[Person]
的单一规范值,即类型 Person
的 Showable
实例,如下面的代码示例所示
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
讨论。