在 GitHub 上编辑此页面

透明特征和类

特质在两个角色中使用

  1. 作为其他类和特质的混合
  2. 作为 vals、defs 或参数的类型

某些特质主要在第一个角色中使用,我们通常不希望在推断类型中看到它们。一个示例是 Product 特质,编译器将其作为混合特质添加到每个案例类或案例对象中。在 Scala 2 中,此父特质有时会使推断类型比应有的更复杂。示例

trait Kind
case object Var extends Kind
case object Val extends Kind
val x = Set(if condition then Val else Var)

在此,x 的推断类型为 Set[Kind & Product & Serializable],而人们希望它是 Set[Kind]。推断此特定类型的推理如下

  • 上面条件的类型是 联合类型 Val | Var。此联合类型被视为“软”,这意味着它不是显式写在源程序中,而是来自形成某些备选方案类型的上限。
  • 在类型推断中,软联合类型会扩展到作为联合类型的超类型的类或特质类型的最小乘积。在示例中,此类型为 Kind & Product & Serializable,因为所有三个特质都是 ValVar 的超特质。因此,该类型成为集合的推断元素类型。

Scala 3 允许将特质或类标记为 transparent,这意味着它可以在类型推断中被抑制。以下是一个遵循上述代码行的示例,但现在使用新的透明特质 S 而不是 Product

transparent trait S
trait Kind
object Var extends Kind, S
object Val extends Kind, S
val x = Set(if condition then Val else Var)

现在 x 的推断类型为 Set[Kind]。公共透明特质 S 不出现在推断类型中。

在前面的示例中,还可以将 Kind 声明为 transparent

transparent trait Kind

if condition then Val else Var 的扩展联合类型将包含透明特质 KindS。在这种情况下,根本不执行扩展,因此 x 的类型将为 Set[Val | Var]

根类和特质 AnyAnyValObjectMatchable 被认为是透明的。这意味着诸如

if condition then 1 else "hello"

这样的表达式将具有类型 Int | String,而不是扩展类型 Any

哪些特质和类是透明的?

通过添加修饰符 transparent 来声明特质和类是透明的。Scala 2 特质和类也可以通过添加 @transparentTrait 注解来声明为透明。此注解在 scala.annotation 中定义。一旦不再需要 Scala 2/3 互操作性,它将被弃用并逐步淘汰。

以下类和特质会自动被视为透明

scala.Any
    scala.AnyVal
    scala.Matchable
    scala.Product
    java.lang.Object
    java.lang.Comparable
    java.io.Serializable

通常,除了根类之外的透明类型是影响继承类的实现和通常不会单独用作类型的特质。标准集合库中的两个示例是

通常,任何递归扩展的特质都是声明为透明的良好候选。

推理规则

透明特质和类可以像往常一样作为显式类型给出。但是,当类型被推断时,它们通常会被省略。大致来说,类型推断的规则暗示了以下内容。

  • 透明特质尽可能从交集中删除。
  • 如果拓宽会导致仅产生透明超类型,则不会拓宽联合类型。

具体规则如下

  • 在推断类型变量的类型、val 的类型或 def 的返回类型时,

  • 其中该类型不是高阶类型,

  • 并且其中 B 是其已知的上界或 Any(如果不存在)

  • 如果到目前为止推断出的类型为 T1 & ... & Tn 的形式,其中 n >= 1,则用 Any 替换最大数量的透明特质 Ti,同时确保结果类型仍然是界限 B 的子类型。

  • 但是,如果所有类型 Ti 都可以通过这种方式替换,则不要执行此拓宽。此条款确保单个透明特质实例(例如 Product)不会拓宽到 Any。只有当透明特质实例与其他一些类型结合出现时,才会将其删除。

  • 如果原始类型是联合类型,并且在先前的步骤中拓宽为仅由透明特质和类组成的乘积,则保留原始联合类型,而不是其拓宽形式。