Scala 3 — 书籍

结构类型

语言
此文档页面特定于 Scala 3,可能涵盖 Scala 2 中不可用的新概念。除非另有说明,本页面中的所有代码示例均假定你使用的是 Scala 3。

Scala 2 具有基于 Java 反射的较弱形式的结构类型,可通过 import scala.language.reflectiveCalls 实现.

简介

某些用例(例如建模数据库访问)在静态类型语言中比在动态类型语言中更尴尬。对于动态类型语言,将行建模为记录或对象并使用简单的点符号选择条目是很自然的,例如 row.columnName

要在静态类型语言中实现相同的体验,需要为数据库操作产生的每一行(包括连接和投影产生的行)定义一个类,并设置一个在行和表示它的类之间进行映射的方案。

这需要大量的样板代码,这导致开发人员为了更简单的方案(其中列名表示为字符串并传递给其他运算符,例如 row.select("columnName"))而放弃静态类型的优势。这种方法放弃了静态类型的优势,并且仍然不如动态类型版本自然。

结构类型在您希望在动态上下文中支持简单的点表示法而又不丧失静态类型的优势时提供帮助。它们允许开发人员使用点表示法并配置如何解析字段和方法。

示例

以下是结构类型 Person 的示例

class Record(elems: (String, Any)*) extends Selectable:
  private val fields = elems.toMap
  def selectDynamic(name: String): Any = fields(name)

type Person = Record {
  val name: String
  val age: Int
}

Person 类型向其父类型 Record 添加了一个细化,该类型定义了 nameage 字段。我们说细化是结构性的,因为 nameage 未在父类型中定义。但它们仍然作为类 Person 的成员存在。例如,以下程序将打印 "Emma is 42 years old."

val person = Record(
  "name" -> "Emma",
  "age" -> 42
).asInstanceOf[Person]

println(s"${person.name} is ${person.age} years old.")

本示例中的父类型 Record 是一个泛型类,可以在其 elems 参数中表示任意记录。此参数是类型为 String 的标签对和类型为 Any 的值的序列。当您将 Person 创建为 Record 时,您必须通过类型转换断言记录定义了正确类型的正确字段。 Record 本身类型太弱,因此编译器在没有用户帮助的情况下无法知道这一点。在实践中,结构类型与其底层泛型表示之间的连接很可能由数据库层完成,因此不会成为最终用户的关注点。

Record 扩展标记特性 scala.Selectable 并定义一个方法 selectDynamic,该方法将字段名映射到其值。通过调用此方法来选择结构类型成员。Scala 编译器将 person.nameperson.age 选择翻译为

person.selectDynamic("name").asInstanceOf[String]
person.selectDynamic("age").asInstanceOf[Int]

第二个示例

为了加深你刚才看到的知识,这里有另一个名为 Book 的结构类型,它表示你可能从数据库中读取的一本书

type Book = Record {
  val title: String
  val author: String
  val year: Int
  val rating: Double
}

Person 一样,这是你创建 Book 实例的方式

val book = Record(
  "title" -> "The Catcher in the Rye",
  "author" -> "J. D. Salinger",
  "year" -> 1951,
  "rating" -> 4.5
).asInstanceOf[Book]

Selectable 类

除了 selectDynamicSelectable 类有时还会定义一个方法 applyDynamic。然后可以使用此方法来翻译结构成员的函数调用。因此,如果 aSelectable 的一个实例,则像 a.f(b, c) 这样的结构调用将转换为

a.applyDynamic("f")(b, c)

此页面的贡献者