透明特征和类
特质在两个角色中使用
- 作为其他类和特质的混合
- 作为 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
,因为所有三个特质都是Val
和Var
的超特质。因此,该类型成为集合的推断元素类型。
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
的扩展联合类型将仅包含透明特质 Kind
和 S
。在这种情况下,根本不执行扩展,因此 x
的类型将为 Set[Val | Var]
。
根类和特质 Any
、AnyVal
、Object
和 Matchable
被认为是透明的。这意味着诸如
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
通常,除了根类之外的透明类型是影响继承类的实现和通常不会单独用作类型的特质。标准集合库中的两个示例是
IterableOps
,它为Iterable
提供方法实现。StrictOptimizedSeqOps
,它针对具有高效索引的序列优化了其中一些实现。
通常,任何递归扩展的特质都是声明为透明的良好候选。
推理规则
透明特质和类可以像往常一样作为显式类型给出。但是,当类型被推断时,它们通常会被省略。大致来说,类型推断的规则暗示了以下内容。
- 透明特质尽可能从交集中删除。
- 如果拓宽会导致仅产生透明超类型,则不会拓宽联合类型。
具体规则如下
-
在推断类型变量的类型、val 的类型或 def 的返回类型时,
-
其中该类型不是高阶类型,
-
并且其中
B
是其已知的上界或Any
(如果不存在) -
如果到目前为止推断出的类型为
T1 & ... & Tn
的形式,其中n >= 1
,则用Any
替换最大数量的透明特质Ti
,同时确保结果类型仍然是界限B
的子类型。 -
但是,如果所有类型
Ti
都可以通过这种方式替换,则不要执行此拓宽。此条款确保单个透明特质实例(例如Product
)不会拓宽到Any
。只有当透明特质实例与其他一些类型结合出现时,才会将其删除。 -
如果原始类型是联合类型,并且在先前的步骤中拓宽为仅由透明特质和类组成的乘积,则保留原始联合类型,而不是其拓宽形式。