在 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

Access 外部,PermissionsPermissionChoice 是不同且無關的類型。

類別的不透明類型成員

雖然通常不透明類型會與物件一起使用,以隱藏模組的實作細節,但它們也可以與類別一起使用。

例如,我們可以將上述對數範例重新定義為一個類別。

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] 範圍內透明(除非類型是頂層定義 - 在這種情況下,它僅在其定義的檔案中透明)。

更多詳細資訊