類型類別 是一種抽象的參數化類型,它允許您在不使用子類型的情況下,為任何封閉資料類型新增新的行為。如果您來自 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
有幾件重要的事情需要指出
- 像
Showable
這樣的類型類別會採用一個類型參數A
來表示我們提供show
實作的類型;相反地,像Show
這樣的傳統特徵並不會這樣做。 - 若要將顯示功能新增至某個類型
A
,傳統特徵需要A extends Show
,而對於類型類別,我們需要有Showable[A]
的實作。 - 在 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
討論。