自從實用Kotlin以後,最近的項目中開始能夠實踐高階類型了,確實能感覺到帶來的優美。但同時這又是個不那麼容易理解的概念,尤爲是Kotlin或者說Java的類型系統中因爲自己不支持,而採用一些取巧的辦法實現高階類型的時候,這個概念變得更加晦澀難懂了。
那麼下面就儘可能已一種通俗易懂的方式帶上實例簡單介紹一下這個概念,以及它在目前應用中的一些使用。java
高階類型 Higher Kinded Type
是相比於普通類型而言的,好比咱們能夠定義這麼一個函數:git
fun testFun(value: List<Int>): List<String> = value.map { it.toString() }
這是一個將一個整數列表List<Int>
變爲字符串列表List<String>
的函數 github
但這個函數只能試用於整數列表,咱們若是這時須要一個處理浮點數列表的就須要再定義一次:算法
fun testFun(value: List<Float>): List<String> = value.map { it.toString() }
但很明顯,咱們寫了重複代碼了,那麼咱們怎麼重構以重用函數的邏輯呢
沒錯,可使用泛型:編程
fun <T> testFun(value: List<T>): List<String> = value.map { it.toString() }
這樣,咱們就能夠處理全部的列表了 segmentfault
可是,這時候又有了另外一個需求,咱們須要可以處理Set
容器的函數:數據結構
fun <T> testFun(value: Set<T>): Set<String> = value.map { it.toString() }.toSet()
或者如今有這麼個需求,咱們須要可以處理全部帶有map方法的容器的函數
,咱們該如何描述?或者說咱們能夠在不修改List或者其餘容器就描述出這麼一個通用函數嗎?
僞代碼:app
fun <C<_> : Mappable, T> testFun(value: C<T>): C<String> = value.map { it.toString() }
這裏的問題在於,Java的泛型系統只能描述一個具體類型,沒法描述一個類型構造
或者說類型函數
,即一種輸入一個類型而後返回一個類型
的類型的函數
,好比: C<_>
能夠當作是描述了一個叫C
的類型函數,若是咱們輸入Int
,則輸出C<Int>
;若是輸入Float
則輸出C<Float>
。
而List
、Set
均可以當作是這樣一種類型函數
,對於List
,若是咱們輸入Int
,則輸出List<Int>
;若是輸入Float
則輸出List<Float>
。
這時,咱們就能夠用C<_>
指代全部這種單參數的類型函數
而這裏的C<_>
就是高階類型,它是類型自己的抽象 jvm
回到上面的問題,咱們能夠描述嗎?能夠,用高階類型就能夠。ide
因爲Java或者Kotlin不支持高階類型,因此咱們要使用一點技巧,能夠當作將高階類型扁平化
這裏就須要使用一個僞類型
:
interface HK<out F, out A>
這時候咱們就能夠描述一個上面所說的函數了:
fun <F, A> testFun(value: HK<F, A>, functor: Functor<F>): HK<F, String> = functor.map(value) { it.toString() }
其中Functor
是一個Typeclass:
@typeclass interface Functor<F> : TC { fun <A, B> map(fa: HK<F, A>, f: (A) -> B): HK<F, B> ... }
那麼怎麼使用呢?
這個函數能夠這麼理解:
凡是實現了Functor實例的類型均可以應用這個函數
它不只能夠用於List
等數據容器,也能夠用於Observable
、Single
等Rx流,還能用於Option
、Either
等數據類型,甚至能夠應用於kotlin.jvm.functions.Function1
函數。是否是以爲這個函數應用範圍至關廣了?
具體怎麼使用呢?
對於List
,首先咱們定義一個List的代理類ListKW
:
class ListKWHK private constructor() typealias ListKWKind<A> = arrow.HK<ListKWHK, A> @higherkind data class ListKW<out A> constructor(val list: List<A>) : ListKWKind<A>, List<A> by list
而後實現List
的Functor
實例(arrow庫自動生成的代碼):
@instance(ListK::class) interface ListKFunctorInstance : Functor<ForListK> { override fun <A, B> map(fa: ListKOf<A>, f: kotlin.Function1<A, B>): ListK<B> = fa.fix().map(f) } object ListKFunctorInstanceImplicits { fun instance(): ListKFunctorInstance = object : ListKFunctorInstance { } }
這樣就能夠用了:
testFun(listKW, instance())
雖然很神奇,但可能有很多人認爲這很多此一舉:有必要寫這麼多額外的代碼嗎?之前沒用高階類型不也寫得好好的嗎?
個人結論是:
Typeclass
(上面有提到過),當咱們使用高階類型,或者說FP
編程思想的時候,代碼的重用方式就相應改變了。它再也不以繼承
爲核心,而是採用組合
的方式,徹底避免沒法多繼承的麻煩問題(FP
中自己就沒有繼承的概念),對於新的Typeclass
,徹底不須要修改原始數據類,只須要實現它對應的實例便可(這部份內容具體講起來就太多了,之後再展開介紹)相比它可能帶來的繁瑣,它所帶來的更高階的抽象能帶來更多的益處,Java中雖然也能夠實現,但Kotlin中實現更加優美,或者說已經具備實用性了
舉例:
Rxjava2中最大的一個改變是,將之前的Observable
給分爲了Single
、Observable
、Maybe
、Flowable
,確實可以更細緻地描述返回數據的類型(是多元素的流
仍是單個值
仍是不肯定是否有的值
),但同時也對之前操做的抽象問題帶來了挑戰。
好比和上面那個問題同樣,實際咱們的某個函數只是須要使用map
函數,但因爲分爲了四種流,咱們不得不每種都重複寫一次:
fun testFun(single: Single<Int>): Single<String> = single.map { if(it > 10) "over 10" else "$it" }
但如今咱們能夠直接描述爲:
fun <F> testFun(fa: HK<F, Int>, functor: Functor<F>): HK<F, String> = functor.map(fa) { if(it > 10) "over 10" else "$it" }
這樣全部實現Functor
Typeclass的類型均可以使用這個函數了
FP
提倡使用已有的數據結構來描述現有問題,這就意味着它的現有數據結構必需要足夠通用。或者換句話說,它提供了比OOP
更高階的算法抽象。
它很靈活,每個Typeclass和DataType都是不少算法的高階抽象,相互組合能夠有無限的可能。
它的代碼組織方式也和OOP
徹底不一樣
這裏淺嘗輒止的介紹了一下高階類型,更多的內容和實用還須要你們本身去理解
參考資料:
arrow
Higher kinded types for Java
Cats
Functional Programming in Scala
fpinkotlin
寫給程序猿的範疇論