不透明类型别名:更多细节
语法
Modifier ::= ...
| ‘opaque’
opaque
是一个 软修饰符。当它不在定义关键字前面时,它仍然可以作为普通标识符使用。
不透明类型别名必须是类、特质或对象的成员,或者它们是在顶层定义的。它们不能在局部块中定义。
类型检查
(单态)不透明类型别名的通用形式是
opaque type T >: L <: U = R
其中下界 L
和上界 U
可能缺失,在这种情况下,它们被假定为分别为 scala.Nothing
和 scala.Any
。如果给出边界,则检查右侧 R
是否符合它们,即 L <: R
和 R <: U
。F-边界不支持不透明类型别名:T
不允许出现在 L
或 U
中。
在别名定义的范围内,别名是透明的:T
被视为 R
的普通别名。在其范围之外,别名被视为抽象类型
type T >: L <: U
如果在对象中定义了不透明类型别名,则会出现特殊情况。示例
object o:
opaque type T = R
在这种情况下,我们在对象内部(对于非不透明类型也是如此)有 o.T
等于 T
或其扩展形式 o.this.T
。这里的相等性被理解为相互子类型化,即 o.T <: o.this.T
和 o.this.T <: o.T
。此外,根据不透明类型别名的规则,我们有 o.this.T
等于 R
。这两个等式组成。也就是说,在 o
内部,也已知 o.T
等于 R
。这意味着以下代码类型检查
object o:
opaque type T = Int
val x: Int = id(2)
def id(x: o.T): o.T = x
不透明类型别名不能是 private
,也不能在子类中被覆盖。不透明类型别名不能具有上下文函数类型作为右侧。
不透明类型的类型参数
不透明类型别名可以有一个类型参数列表。以下别名是格式良好的
opaque type F[T] = (T, T)
opaque type G = [T] =>> List[T]
但以下不是
opaque type BadF[T] = [U] =>> (T, U)
opaque type BadG = [T] =>> [U] =>> (T, U)
相等性的转换
使用 ==
或 !=
比较两个不透明类型的值通常使用通用相等性,除非为该类型定义了另一个重载的 ==
或 !=
运算符。为了避免装箱,该操作在类型检查后被映射到底层类型上定义的(不)相等运算符。例如,
opaque type T = Int
...
val x: T
val y: T
x == y // uses Int equality for the comparison.
顶层不透明类型
顶层的不透明类型别名在其出现的源文件中所有其他顶层定义中都是透明的,但在嵌套对象和类以及所有其他源文件中是不透明的。示例
// in test1.scala
opaque type A = String
val x: A = "abc"
object obj:
val y: A = "abc" // error: found: "abc", required: A
// in test2.scala
def z: String = x // error: found: A, required: String
如果回想一下顶层定义被放置在它们自己的合成对象中,这种行为就会变得清晰。例如,test1.scala
中的代码将扩展为
object test1$package:
opaque type A = String
val x: A = "abc"
object obj:
val y: A = "abc" // error: cannot assign "abc" to opaque type alias A
不透明类型别名 A
在其范围内是透明的,其中包括 x
的定义,但不包括 obj
和 y
的定义。
与 SIP 35 的关系
Scala 3 中的不透明类型是对 Scala SIP 35 中描述内容的演进。
与该 SIP 中描述的状态相比,存在以下差异:
- 不透明类型别名不再可以在局部语句序列中定义。
- 不透明类型别名可见的范围现在是其定义的整个范围,而不是仅仅是伴生对象。
- 不透明类型别名的伴生对象的概念已被删除。
- 不透明类型别名可以具有边界。
- 涉及不透明类型别名的类型相等的概念已得到澄清。它相对于 SIP 35 的先前实现得到了加强。