不透明类型别名
不透明类型别名提供了零开销类型抽象。例如:
object MyMath {
opaque type Logarithm = Double
object Logarithm {
// These are the two ways to lift to the Logarithm type
def apply(d: Double): Logarithm = math.log(d)
def safe(d: Double): Option[Logarithm] =
if (d > 0.0) Some(math.log(d)) else None
}
// Extension methods define opaque types' public APIs
extension (x: Logarithm) {
def toDouble: Double = math.exp(x)
def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y))
def * (y: Logarithm): Logarithm = x + y
}
}
这引入了 Logarithm
作为一种新的抽象类型,它被实现为 Double
。 Logarithm
与 Double
相同的事实仅在定义 Logarithm
的作用域中才知道, 在上面的示例中该作用域为对象 MyMath
内部。换句话说,在作用域内它被视为类型别名, 但在外界这是不透明的,因此 Logarithm
被视为抽象类型,与 Double
无关。
Logarithm
的公共 API 由定义在伴生对象中的 apply
和 safe
方法组成。 它们把值从 Double
转换到 Logarithm
。此外,toDouble
操作符以另一种方式进行转换, 以及操作符 +
和 *
被定义为 Logarithm
值的扩展方法。 以下操作将是有效的,因为它们使用 MyMath
对象中实现的功能。
import MyMath.Logarithm
val l = Logarithm(1.0)
val l2 = Logarithm(2.0)
val l3 = l * l2
val l4 = l + l2
但以下操作将导致类型错误:
val d: Double = l // error: found: Logarithm, required: Double
val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm
l * 2 // error: found: Int(2), required: Logarithm
l / l2 // error: `/` is not a member of Logarithm
不透明类型别名的界定
不透明类型别名也可以带有界定。例如:
object Access {
opaque type Permissions = Int
opaque type PermissionChoice = Int
opaque type Permission <: Permissions & PermissionChoice = Int
extension (x: Permissions)
def & (y: Permissions): Permissions = x | y
extension (x: PermissionChoice)
def | (y: PermissionChoice): PermissionChoice = x | y
extension (granted: Permissions)
def is(required: Permissions) = (granted & required) == required
extension (granted: Permissions)
def isOneOf(required: PermissionChoice) = (granted & required) != 0
val NoPermission: Permission = 0
val Read: Permission = 1
val Write: Permission = 2
val ReadWrite: Permissions = Read | Write
val ReadOrWrite: PermissionChoice = Read | Write
}
Access
对象定义了三个不透明类型别名:
Permission
,表示单个权限。Permissions
,表示一组权限,含义是“授予其中所有权限”。PermissionChoice
,表示一组权限,含义是“至少授予其中之一权限”。
在 Access
对象外,Permissions
类型的值可以使用 &
操作符组合, 其中 x & y
表示“授予 x
和 y
中的权限”。 PermissionChoice
类型的值可使用 |
操作符组合, 其中 x | y
表示“授予 x
或 y
中的权限”。
注意,在 Access
对象内部,&
和 |
操作符总是被解析为 Int
的相应方法, 因为成员方法总是优先于扩展方法。因此 Access
中的 |
扩展方法不会导致无限递归。 此外,ReadWrite
的定义必须使用 |
,即使在 Access
外部的等效定义使用 &
。
三个不透明类型别名都具有相同的基础表示类型 Int
。Permission
类型具有上限 Permissions & PermissionChoice
。 这使得在 Access
对象外部知道 Permission
是另外两个类型的子类型。 因此下面的使用场景能通过类型检查。
object User {
import Access.*
case class Item(rights: Permissions)
val roItem = Item(Read) // OK, since Permission <: Permissions
val rwItem = Item(ReadWrite)
val noItem = Item(NoPermission)
assert(!roItem.rights.is(ReadWrite))
assert(roItem.rights.isOneOf(ReadOrWrite))
assert(rwItem.rights.is(ReadWrite))
assert(rwItem.rights.isOneOf(ReadOrWrite))
assert(!noItem.rights.is(ReadWrite))
assert(!noItem.rights.isOneOf(ReadOrWrite))
}
另一方面,调用 roItem.rights.isOneOf(ReadWrite)
会产生一个类型错误, 因为在 Access
外,Permissions
和 PermissionChoice
是不同的、不相关的类型。
类的不透明类型成员
通常不透明类型与对象一起使用,目的是隐藏模块的实现细节,但它也可以和类一起使用。
例如,我们可以把上面示例中的 Logarithms
重新定义为一个类:
class Logarithms {
opaque type Logarithm = Double
def apply(d: Double): Logarithm = math.log(d)
def safe(d: Double): Option[Logarithm] =
if d > 0.0 then Some(math.log(d)) else None
def mul(x: Logarithm, y: Logarithm) = x + y
}
不同实例的不透明类型成员被视为不同的:
val l1 = new Logarithms
val l2 = new Logarithms
val x = l1(1.5)
val y = l1(2.6)
val z = l2(3.1)
l1.mul(x, y) // type checks
l1.mul(x, z) // error: found l2.Logarithm, required l1.Logarithm
一般来说,可以认为不透明类型仅在 private[this]
作用域内是透明的。