Scala 中 Type Class 實(shí)現(xiàn)的套路
什么是Type Class
A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass,
that means that it supports and implements the behavior the typeclass describes. A lot of people
coming from OOP get confused by typeclasses because they think they are like classes in object
oriented languages. Well, they're not. You can think of them kind of as Java interfaces, only better.
TypeClass
是 Haskell
語(yǔ)言里的概念婴噩,根據(jù)《Learn you a haskell》中的解釋, TypeClass
是定義一些公共行
為的接口癞谒,=Java= 語(yǔ)言中最為接近的概念是 interface
, Scala
語(yǔ)言中最為接近的概念是 trait
.
在 Haskell
中蕊爵,可以給任意類型實(shí)現(xiàn)任意 TypeClass
, 而在 Java
中只能通過(guò)實(shí)現(xiàn)某個(gè)接口才能讓 Class
具有接口定義
的行為, 如果要給某個(gè)類庫(kù)的類型添加額外的行為框杜,幾乎是不可能的事情(通過(guò)代碼生成技術(shù)可以).
為什么需要Type Class
TypeClass
將 行為定義 與 具有行為的對(duì)象 分離,更容易實(shí)現(xiàn) DuckType
; 同時(shí), 在函數(shù)式編程中,通常將數(shù)據(jù)與行為
相分離问麸,甚至是數(shù)據(jù)與行為按需綁定疗杉,已達(dá)到更為高級(jí)的組合特性.
Scala 中對(duì) TypeClass的實(shí)現(xiàn)<a id="sec-1-3" name="sec-1-3"></a>
Scala
是基于JVM平臺(tái)的語(yǔ)言阵谚,受JVM的限制,不具備像 Haskell
那樣語(yǔ)言原生對(duì) TypeClass
的支持烟具, Scala
語(yǔ)言使用了
隱式轉(zhuǎn)換的魔法梢什,使之能夠不那么完美地支持 TypeClass
.
Scala
中實(shí)現(xiàn) TypeClass
可總結(jié)為三板斧.
1. TypeClass Trait 定義
TypeClass定義即使用 trait
來(lái)定義相關(guān)行為接口,比如 半群
:
trait Semigroup[A] {
def combine(a1: A, a2: A): A
}
2. 定義 TypeClass 隱式實(shí)例
針對(duì)需要實(shí)現(xiàn) TypeClass
的隱式實(shí)例朝聋,比如我們實(shí)現(xiàn) Int
加法半群嗡午,根據(jù) Scala
中 implicit
隱式的實(shí)現(xiàn),會(huì)在伴生對(duì)象中
自動(dòng)查找隱式實(shí)例冀痕,所以一般講實(shí)例定義在 TypeClass
的伴生對(duì)象中荔睹,這樣只要 import
TypeClass
就可以獲得隱式實(shí)例:
object Semigroup {
implicit val intPlusInstance = new Semigroup[Int] {
def combine(a1: Int, a2: Int): Int = a1 + a2
}
}
一般地,還會(huì)在伴生對(duì)象中增加 apply
只能構(gòu)造器來(lái)生成 TypeClass
實(shí)例言蛇,而 apply
方法可以省略僻他,代碼看起來(lái)會(huì)更加簡(jiǎn)潔:
object Semigroup {
def apply[A](implicit instance: Semigroup[A]) = instance
implicit val intPlusInstance = new Semigroup[Int] {
def combine(a1: Int, a2: Int): Int = a1 + a2
}
}
這樣,我們就可以通過(guò)伴生對(duì)象腊尚,獲取 TypeClass
實(shí)例吨拗,從而調(diào)用 TypeClass
的行為方法:
import Semigroup._
Semigroup[Int].combine(1, 2) // 3
3. 定義 TypeClass 語(yǔ)法結(jié)構(gòu)
為了進(jìn)一步簡(jiǎn)化代碼,我們可以定義一些語(yǔ)法結(jié)構(gòu)婿斥,來(lái)隱藏 TypeClass
的樣板式的代碼劝篷,如:
Semigroup[Int].combine(1, 2)
, 我們希望通過(guò)簡(jiǎn)單的表達(dá)式來(lái)表示: 1 |+| 2
. 如何來(lái)
實(shí)現(xiàn)呢?
object Semigroup {
def apply[A](implicit instance: Semigroup[A]) = instance
implicit val intPlusInstance = new Semigroup[Int] {
def combine(a1: Int, a2: Int): Int = a1 + a2
}
// 增加語(yǔ)法結(jié)構(gòu)
abstract class Syntax[A](implicit I: Semigroup[A]) {
def a1: A
def |+|(a2: A): A = I.combine(a1, a2)
}
// 定義隱式轉(zhuǎn)換方法民宿,將
implicit def to[A](target: A)(implicit I: Semigroup[A]): Syntax[A] = new Syntax[A] {
val a1 = target
}
}
定義完成后携龟,我們就可以使用新的語(yǔ)法 |+|
來(lái)調(diào)用 Semigroup
這個(gè) TypeClass
的方法了:
import Semigroup._
Semigroup[Int].combine(1, 2) // 3
// 使用語(yǔ)法操作符
1 |+| 2 // 3
改進(jìn),更簡(jiǎn)潔
將上述實(shí)現(xiàn)綜合一下勘高,實(shí)現(xiàn)一個(gè) Semigroup
TypeClass
需要以下代碼:
trait Semigroup[A] {
def combine(a1: A, a2: A): A
}
object Semigroup {
def apply[A](implicit instance: Semigroup[A]) = instance
implicit val intPlusInstance = new Semigroup[Int] {
def combine(a1: Int, a2: Int): Int = a1 + a2
}
// 增加語(yǔ)法結(jié)構(gòu)
abstract class Syntax[A](implicit I: Semigroup[A]) {
def a1: A
def |+|(a2: A): A = I.combine(a1, a2)
}
// 定義隱式轉(zhuǎn)換方法峡蟋,將
implicit def to[A](target: A)(implicit I: Semigroup[A]): Syntax[A] =
new Syntax[A] {
val a1 = target
}
}
仔細(xì)分析坟桅,這里面有一些樣板代碼,比如伴生對(duì)象的 apply
方法蕊蝗、語(yǔ)法結(jié)構(gòu)的類和隱式轉(zhuǎn)換方法仅乓,
這對(duì)于所有的類型類都一樣,都要寫這些重復(fù)的代碼蓬戚。去掉這些樣板代碼夸楣,我們真正需要的是:
-
TypeClass
的定義 -
TypeClass
的類型實(shí)例 -
TypeClass
的語(yǔ)法操作符
得益于 Scala
元編程的強(qiáng)大,我們可以通過(guò) Macro
來(lái)自動(dòng)生成這些樣板代碼子漩。 Simulacrum
項(xiàng)目就是專注于此豫喧。
利用 Simulacrum
, 我們可以將我們的代碼改進(jìn)為
import simulacrum._
@typeclass trait Semigroup[A] {
@op("|+|") def combine(a1: A, a2: A): A
}
// 定義隱式實(shí)例
implicit val intPlusInstance = new Semigroup[Int] {
def combine(a1: Int, a2: Int): Int = a1 + a2
}
// 使用
import Semigroup.ops._
1 |+| 2 // 3
參考資料
- Simulacrum: https://github.com/mpilquist/simulacrum
- TypeClass in Cats: http://typelevel.org/cats/typeclasses.html
- About Scala Macro: http://docs.scala-lang.org/overviews/macros/overview.html