Scala 中 Type Class 實(shí)現(xiàn)的套路

typeclass.png

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.

TypeClassHaskell 語(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ù) Scalaimplicit 隱式的實(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ù)的代碼蓬戚。去掉這些樣板代碼夸楣,我們真正需要的是:

  1. TypeClass 的定義
  2. TypeClass 的類型實(shí)例
  3. 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

參考資料

  1. Simulacrum: https://github.com/mpilquist/simulacrum
  2. TypeClass in Cats: http://typelevel.org/cats/typeclasses.html
  3. About Scala Macro: http://docs.scala-lang.org/overviews/macros/overview.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市幢泼,隨后出現(xiàn)的幾起案子紧显,更是在濱河造成了極大的恐慌,老刑警劉巖缕棵,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孵班,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡招驴,警方通過(guò)查閱死者的電腦和手機(jī)篙程,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)别厘,“玉大人虱饿,你說(shuō)我怎么就攤上這事〈ヅ浚” “怎么了郭厌?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)雕蔽。 經(jīng)常有香客問(wèn)我折柠,道長(zhǎng),這世上最難降的妖魔是什么批狐? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任扇售,我火速辦了婚禮,結(jié)果婚禮上嚣艇,老公的妹妹穿的比我還像新娘承冰。我一直安慰自己,他們只是感情好食零,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布困乒。 她就那樣靜靜地躺著,像睡著了一般贰谣。 火紅的嫁衣襯著肌膚如雪娜搂。 梳的紋絲不亂的頭發(fā)上迁霎,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音百宇,去河邊找鬼考廉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛携御,可吹牛的內(nèi)容都是我干的昌粤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼啄刹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼涮坐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起誓军,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤袱讹,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后谭企,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡评肆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年债查,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓜挽。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盹廷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出久橙,到底是詐尸還是另有隱情俄占,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布淆衷,位于F島的核電站缸榄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏祝拯。R本人自食惡果不足惜甚带,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望佳头。 院中可真熱鬧鹰贵,春花似錦、人聲如沸康嘉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)亭珍。三九已至敷钾,卻和暖如春枝哄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闰非。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工膘格, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人财松。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓瘪贱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親辆毡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子菜秦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Scala中的implicit關(guān)鍵字對(duì)于我們初學(xué)者像是一個(gè)謎一樣的存在,一邊驚訝于代碼的簡(jiǎn)潔舶掖,一邊像在迷宮里打轉(zhuǎn)一...
    大刀閱讀 18,519評(píng)論 10 18
  • 這篇講義只講scala的簡(jiǎn)單使用球昨,目的是使各位新來(lái)的同事能夠首先看懂程序,因?yàn)?scala 有的語(yǔ)法對(duì)于之前使用習(xí)...
    MrRobot閱讀 2,914評(píng)論 0 10
  • 除了在 Predef 對(duì)象中自動(dòng)加載的那些隱式對(duì)象外眨攘,其他在源碼中出現(xiàn)的隱式對(duì)象均不是本地對(duì)象主慰。[P112] 隱式...
    云之外閱讀 645評(píng)論 0 0
  • 戲路如流水,從始至終鲫售,點(diǎn)滴不漏共螺。一路百折千回,本性未變情竹,終歸大海藐不。一步一戲,一轉(zhuǎn)身一變臉秦效,撲朔迷離雏蛮。真心自然流露,...
    劉光聰閱讀 2,026評(píng)論 3 13
  • Adora Cheung衷模, Homejoy的創(chuàng)始人你該成為你所在行業(yè)的行家找到真正的痛點(diǎn),一句話描述它蒲赂,并讓自己的...
    LeaChau閱讀 360評(píng)論 0 2