Scala的Higher-Kinded类型
Scala的Higher-Kinded類型
Higher-Kinded從字面意思上看是更高級的分類,也就是更高一級的抽象。我們先看個例子。
如果我們要在scala中實現一個對Seq[Int]的sum方法,應該怎么做呢?
def sum(seq: Seq[Int]): Int = seq reduce (_ + _)sum(Vector(1,2,3,4,5)) // 結果值: 15看起來很簡單,剛剛我們實現了Seq[Int]的sum操作,那么如果我們想更進一步,我們想同時實現Seq[Int]和Seq[(Int,Int)]的操作該怎么處理呢?
不同的Seq需要不同的add實現,我們先抽象一個trait:
trait Add[T] { def add(t1: T, T2: T): T }接下來我們在Add的伴生類中定義兩個隱式實例,一個Add[Int], 一個Add[(Int,Int)]。
object Add { implicit val addInt = new Add[Int] { def add(i1: Int, i2: Int): Int = i1 + i2 } implicit val addIntIntPair = new Add[(Int,Int)] { def add(p1: (Int,Int), p2: (Int,Int)): (Int,Int) = (p1._1 + p2._1, p1._2 + p2._2) } }這兩個隱式實例分別為Add[Int], 一個Add[(Int,Int)]實現了add方法。
最后我們可以定義sumseq方法了:
def sumSeq[T : Add](seq: Seq[T]): T = seq reduce (implicitly[Add[T]].add(_,_))T : Add 被稱為 上下文定界( context bound), 它暗指隱式參數列表將接受Add[T] 實例。
我們看下怎么調用:
sumSeq(Vector(1 -> 10, 2 -> 20, 3 -> 30)) // 結果值: (6,60) sumSeq(1 to 10) // 結果值: 55 sumSeq(Option(2)) // 出錯!sumSeq可以接受Seq[Int]和Seq[(Int,Int)]類型,但是無法接收Option。
對于任何一種序列,只要我們為它定義了隱式的Add 實例,那么sumSeq 方法便能計算出該序列的總和。
不過,sumSeq 仍然只支持Seq 子類型。假如容器類型并不是Seq 子類型,但是卻實現了reduce 方法,我們該如何對該容器進行處理呢?我們會使用更加泛化的求和操作。這時候就需要使用到higher-kinded 類型了。
我們用M替代Seq,則可以得到M[T],M[T]就是本文介紹的Higher-Kinded類型。
trait Reduce[T, -M[T]] { def reduce(m: M[T])(f: (T, T) => T): T } object Reduce { implicit def seqReduce[T] = new Reduce[T, Seq] { def reduce(seq: Seq[T])(f: (T, T) => T): T = seq reduce f } implicit def optionReduce[T] = new Reduce[T, Option] { def reduce(opt: Option[T])(f: (T, T) => T): T = opt reduce f } }為了能對Seq 和Option 值執行reduce操作,我們分別為這兩類類型提供了隱式實例。為了簡化起見,我們將直接使用類型中已經提供的reduce 方法執行reduce操作。
注意這里-M[T]是逆變類型,還記得我們之前的結論嗎?函數的參數一定是逆變類型的。 因為M[T]是reduce(m: M[T])的參數,所以我們需要定義它為逆變類型-M[T]。
我們看一下sum方法該怎么定義:
def sum[T : Add, M[T]](container: M[T])( implicit red: Reduce[T,M]): T = red.reduce(container)(implicitly[Add[T]].add(_,_))調用結果如下:
sum(Vector(1 -> 10, 2 -> 20, 3 -> 30)) // 結果值: (6,60) sum(1 to 10) // 結果值: 55 sum(Option(2)) // 結果值: 2 sum[Int,Option](None) // 錯誤!最后一個調用,我們為sum 方法添加的類型簽名[Int, Opton] 會要求編譯器將None 解釋成Option[Int] 類型。假如不添加該類型簽名,我們將得到編譯錯誤:無法判斷Option[T] 類型中的類型參數T 到底應該對應addInt 方法還是addIntIntPair 方法。
通過顯式地指定類型,我們能夠得到真正希望捕獲的運行錯誤:我們不能對None 值調用reduce 方法。
在上面的sum方法中,sum[T : Add, M[T]], T: Add是上下文邊界,我們也想定義M[T] 的上下文邊界,比如M[T] : Reduce。
因為上下文邊界只適用于包含單參數的場景,而Reduce 特征包
含兩個類型參數,所以我們需要對Reduce進行改造:
在新的reduce1中,只包含一個類型參數且屬于higher-kinded 類型。
M[_]是上篇文章我們講到的存在類型。T 參數被移至reduce 方法。
修改后的sum方法如下:
def sum[T : Add, M[_] : Reduce1](container: M[T]): T = implicitly[Reduce1[M]].reduce(container)(implicitly[Add[T]].add(_,_))我們定義了兩個上下文邊界,它們分別作用于Reduce1 和Add。而使用implicity 修飾的類型參數則能夠區分出這兩種不同的隱式值。
M[_]就是我們經常會看到的higher-kinded, higher-kinded雖然帶給我們額外的抽象,但是使代碼變得更加復雜。大家可以酌情考慮是否需要使用。
更多精彩內容且看:
- 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
- Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
- Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
- java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程
更多教程請參考 flydean的博客
總結
以上是生活随笔為你收集整理的Scala的Higher-Kinded类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Scala的存在类型
- 下一篇: Spring Boot @EnableA