Scala 3 — 書籍

類型類別

語言

類型類別 是一種抽象的參數化類型,它允許您在不使用子類型的情況下,為任何封閉資料類型新增新的行為。如果您來自 Java,您可以將類型類別視為類似 java.util.Comparator[T] 的東西。

Oliveira 等人在 2010 年發表的論文 “類型類別作為物件和隱含” 討論了 Scala 中類型類別背後的基本概念。儘管該論文使用較舊版本的 Scala,但這些概念至今仍適用。

類型類別在多種使用案例中很有用,例如

  • 表達您不擁有的類型(來自標準程式庫或第三方程式庫)如何符合此行為
  • 為多個類型表達此類行為,而不涉及這些類型之間的子類型關係

類型類別是具有多個參數的特徵,其實作在 Scala 3 中提供為 given 實例,或在 Scala 2 中提供為 implicit 值。

範例

例如,Show 是 Haskell 中一個眾所周知的類型類別,而以下程式碼顯示在 Scala 中實作它的其中一種方式。如果您想像 Scala 類別沒有 toString 方法,您可以定義一個 Show 類型類別,以將此行為新增至您想要轉換為自訂字串的任何類型。

類型類別

建立類型類別的第一個步驟是宣告一個參數化特徵,其中包含一個或多個抽象方法。由於 Showable 僅有一個名為 show 的方法,因此它寫成像這樣

// a type class
trait Showable[A] {
  def show(a: A): String
}
// a type class
trait Showable[A]:
  extension (a: A) def show: String

請注意,這種方法接近於一般的物件導向方法,在這種方法中,您通常會定義一個特徵 Show,如下所示

// a trait
trait Show {
  def show: String
}
// a trait
trait Show:
  def show: String

有幾件重要的事情需要指出

  1. Showable 這樣的類型類別會採用一個類型參數 A 來表示我們提供 show 實作的類型;相反地,像 Show 這樣的傳統特徵並不會這樣做。
  2. 若要將顯示功能新增至某個類型 A,傳統特徵需要 A extends Show,而對於類型類別,我們需要有 Showable[A] 的實作。
  3. 在 Scala 3 中,為了在模仿 Show 語法的 Showable 中允許相同的函式呼叫語法,我們將 Showable.show 定義為擴充函式。

實作具體實例

下一步是決定應用程式中的哪些類別 Showable 應適用,然後為它們實作該行為。例如,要為此 Person 類別實作 Showable

case class Person(firstName: String, lastName: String)

您將定義 Showable[Person] 型別的單一正規值,即 Person 型別的 Showable 實例,如下列程式碼範例所示

implicit val showablePerson: Showable[Person] = new Showable[Person] {
  def show(p: Person): String =
    s"${p.firstName} ${p.lastName}"
}
given Showable[Person] with
  extension (p: Person) def show: String =
    s"${p.firstName} ${p.lastName}"

使用型別類別

現在您可以像這樣使用此型別類別

val person = Person("John", "Doe")
println(showablePerson.show(person))

請注意,在實務上,型別類別通常用於型別未知的值,這與 Person 型別不同,如下一節所示。

val person = Person("John", "Doe")
println(person.show)

同樣地,如果 Scala 沒有 toString 函式可供每個類別使用,您可以使用此技術將 Showable 行為新增到您想要轉換為 String 的任何類別。

撰寫使用型別類別的函式

與繼承一樣,您可以定義使用 Showable 作為型別參數的函式

def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit =
  as.foreach(a => println(showable.show(a)))

showAll(List(Person("Jane"), Person("Mary")))
def showAll[A: Showable](as: List[A]): Unit =
  as.foreach(a => println(a.show))

showAll(List(Person("Jane"), Person("Mary")))

具有多個函式的型別類別

請注意,如果您想要建立一個具有多個方法的類型類別,初始語法看起來像這樣

trait HasLegs[A] {
  def walk(a: A): Unit
  def run(a: A): Unit
}
trait HasLegs[A]:
  extension (a: A)
    def walk(): Unit
    def run(): Unit

真實世界的範例

有關在 Scala 3 中使用類型類別的真實世界範例,請參閱 多重宇宙等式區段 中的 CanEqual 討論。

此頁面的貢獻者