在 GitHub 上编辑此页面

不透明类型别名

不透明类型别名提供类型抽象,没有任何开销。示例

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 then Some(math.log(d)) else None

  end Logarithm

  // 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

end MyMath

这将 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: PermissionChoice)
    def | (y: PermissionChoice): PermissionChoice = x | y
  extension (x: Permissions)
    def & (y: Permissions): Permissions = x | y
  extension (granted: Permissions)
    def is(required: Permissions) = (granted & required) == required
    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

end Access

Access 对象定义了三个不透明类型别名

  • Permission,表示单个权限,
  • Permissions,表示一组权限,其含义为“授予所有这些权限”,
  • PermissionChoice,表示一组权限,其含义为“至少授予其中一个权限”。

Access 对象外部,可以使用 & 运算符组合 Permissions 类型的的值,其中 x & y 表示“x y 中的所有权限都已授予”。可以使用 | 运算符组合 PermissionChoice 类型的的值,其中 x | y 表示“x y 中的权限已授予”。

请注意,在 Access 对象内部,&| 运算符始终解析为 Int 的相应方法,因为成员始终优先于扩展方法。因此,Access 中的 | 扩展方法不会导致无限递归。

特别是,ReadWrite 的定义必须使用 |,即 Int 的按位运算符,即使 Access 外部的客户端代码将使用 &,即 Permissions 上的扩展方法。ReadWriteReadOrWrite 的内部表示是相同的,但客户端不可见,客户端仅对 Permissions 的语义感兴趣,如下面的示例所示。

所有三个不透明类型别名具有相同的底层表示类型IntPermission类型具有上限Permissions & PermissionChoice。这使Access对象外部知道Permission是其他两个类型的子类型。因此,以下用法场景类型检查。

object User:
  import Access.*

  case class Item(rights: Permissions)
  extension (item: Item)
    def +(other: Item): Item = Item(item.rights & other.rights)

  val roItem = Item(Read)  // OK, since Permission <: Permissions
  val woItem = Item(Write)
  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))

  assert((roItem + woItem).rights.is(ReadWrite))
end User

另一方面,调用roItem.rights.isOneOf(ReadWrite)将产生类型错误

assert(roItem.rights.isOneOf(ReadWrite))
                               ^^^^^^^^^
                               Found:    (Access.ReadWrite : Access.Permissions)
                               Required: Access.PermissionChoice

PermissionsPermissionChoiceAccess外部的不同、不相关的类型。

类上的不透明类型成员

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

例如,我们可以将对数的上述示例重新定义为一个类。

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]范围内透明(除非该类型是顶级定义 - 在这种情况下,它仅在其定义的文件中透明)。

更多详情