类型类 是一种抽象的参数化类型,它允许您在不使用子类型的情况下向任何封闭数据类型添加新行为。
如果你从 Java 那边过来,你可以将类型类视为类似于 java.util.Comparator[T]
的东西。
Oliveira 等人写的论文 “Type Classes as Objects and Implicits” (2010) 讨论了在 Scala 中类型类背后的基本观点。 虽然论文用了旧的 Scala 版本,但其中的观点至今依然有用。
这在多用例中很有用,例如:
- 表达你不拥有的类型——来自标准库或第三方库——如何符合这种行为
- 为多种类型表达这种行为,而不涉及这些类型之间的子类型关系
类型类只是具有一个或多个参数的 traits,其实现在 Scala 3 中,由 given
实例提供,在 Scala 2 中用 implicit
值。
例子
例如,Show
是 Haskell 中众所周知的类型类,下面的代码显示了在 Scala 中实现它的一种方法。
如果您认为 Scala 类没有 toString
方法,您可以定义一个 Show
类型类,然后把此行为添加到任意的类,这个类是能够转换为自定义字符串。
类型类
创建类型类的第一步是声明具有一个或多个抽象方法的参数化 trait。
因为 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
trait时,下面这样的办法接近普通的面向对象的办法:
// a trait
trait Show {
def show: String
}
// a trait
trait Show:
def show: String
有几件重要的事情需要指出:
- 像
Showable
这样的类型类有一个类型参数A
来说明我们为哪种类型提供了show
的实现;相反,像Show
这样的传统的 trait 不会。 - 要将 show 功能添加到特定类型
A
,传统的 trait 需要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
方法,您可以使用此技术将 Showable
行为添加到您希望能够转换为 String
的任何类。
编写使用类型类的方法
与继承一样,您可以定义使用 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(x => 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
讨论。
Contributors to this page:
Contents
- 导言
- Scala 3 特性
- 为什么是 Scala 3 ?
- Scala 的味道
- Hello, World!
- The REPL
- 变量和数据类型
- 控制结构
- 领域建模
- 方法
- 头等函数
- 单例对象
- 集合
- 上下文抽象
- 顶层定义
- 总结
- 类型初探
- 字符串插值
- 控制结构
- 领域建模
- 工具
- OOP 领域建模
- 函数式领域建模
- 方法
- 方法特性
- main 方法
- 总结
- 函数
- 匿名函数
- 函数变量
- Eta 扩展
- 高阶函数
- 自定义 map 函数
- 创建可以返回函数的方法
- 总结
- 打包和导入
- Scala 集合
- 集合类型
- 集合方法
- 总结
- 函数式编程
- 什么是函数式编程?
- 不可变值
- 纯函数
- 函数是值
- 函数式错误处理
- 总结
- 类型和类型系统
- 类型推断
- 泛型
- 相交类型
- 联合类型
- 代数数据类型
- 型变
- 不透明类型
- 结构化类型
- 依赖函数类型
- 其他类型
- 上下文抽象
- 扩展方法
- Given 实例和 Using 语句
- 上下文绑定
- Given 导入
- 实现类型类
- 多元相等性
- 隐式转换
- 总结
- 并发
- Scala 工具
- 使用 sbt 构建和测试 Scala 项目
- worksheet
- 与 Java 交互
- 向 Java 开发者介绍Scala
- Scala for JavaScript Developers
- Scala for Python Developers
- 下一步去哪