Link Search Menu Expand Document

不透明类型别名

不透明类型别名提供了零开销类型抽象。例如:

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 作为一种新的抽象类型,它被实现为 DoubleLogarithmDouble 相同的事实仅在定义 Logarithm 的作用域中才知道, 在上面的示例中该作用域为对象 MyMath 内部。换句话说,在作用域内它被视为类型别名, 但在外界这是不透明的,因此 Logarithm 被视为抽象类型,与 Double 无关。

Logarithm 的公共 API 由定义在伴生对象中的 applysafe 方法组成。 它们把值从 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 外部的等效定义使用 &

三个不透明类型别名都具有相同的基础表示类型 IntPermission 类型具有上限 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 外,PermissionsPermissionChoice 是不同的、不相关的类型。

类的不透明类型成员

通常不透明类型与对象一起使用,目的是隐藏模块的实现细节,但它也可以和类一起使用。

例如,我们可以把上面示例中的 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] 作用域内是透明的。

更多细节