高階類型帶來了什麼

自從實用Kotlin以後,最近的項目中開始能夠實踐高階類型了,確實能感覺到帶來的優美。但同時這又是個不那麼容易理解的概念,尤爲是Kotlin或者說Java的類型系統中因爲自己不支持,而採用一些取巧的辦法實現高階類型的時候,這個概念變得更加晦澀難懂了。
那麼下面就儘可能已一種通俗易懂的方式帶上實例簡單介紹一下這個概念,以及它在目前應用中的一些使用。java

下面都是使用Kotlin進行講解


什麼是高級類型

高階類型 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>
ListSet均可以當作是這樣一種類型函數,對於List,若是咱們輸入Int,則輸出List<Int>;若是輸入Float則輸出List<Float>
這時,咱們就能夠用C<_>指代全部這種單參數的類型函數
而這裏的C<_>就是高階類型,它是類型自己的抽象 jvm

回到上面的問題,咱們能夠描述嗎?能夠,用高階類型就能夠。ide


Kotlin上的實現

因爲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等數據容器,也能夠用於ObservableSingle等Rx流,還能用於OptionEither等數據類型,甚至能夠應用於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

而後實現ListFunctor實例(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())

實際使用

雖然很神奇,但可能有很多人認爲這很多此一舉:有必要寫這麼多額外的代碼嗎?之前沒用高階類型不也寫得好好的嗎?

個人結論是:

  1. 上面的模板代碼確實很煩,但這是個一勞永逸的工做,寫一次之後對這個類型就都不用寫了。並且還已經有庫幫咱們所有寫完了,那就是arrow
  2. 高階類型因爲更高的抽象度,確實能有效減小重複代碼,甚至實現之前沒法實現的抽象(下面會提到),簡單說:優美
  3. 提到高階類型就不得不說Typeclass(上面有提到過),當咱們使用高階類型,或者說FP編程思想的時候,代碼的重用方式就相應改變了。它再也不以繼承爲核心,而是採用組合的方式,徹底避免沒法多繼承的麻煩問題(FP中自己就沒有繼承的概念),對於新的Typeclass,徹底不須要修改原始數據類,只須要實現它對應的實例便可(這部份內容具體講起來就太多了,之後再展開介紹)

相比它可能帶來的繁瑣,它所帶來的更高階的抽象能帶來更多的益處,Java中雖然也能夠實現,但Kotlin中實現更加優美,或者說已經具備實用性了

舉例:
Rxjava2中最大的一個改變是,將之前的Observable給分爲了SingleObservableMaybeFlowable,確實可以更細緻地描述返回數據的類型(是多元素的流仍是單個值仍是不肯定是否有的值),但同時也對之前操做的抽象問題帶來了挑戰。

好比和上面那個問題同樣,實際咱們的某個函數只是須要使用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"
        }

這樣全部實現FunctorTypeclass的類型均可以使用這個函數了


結語

FP提倡使用已有的數據結構來描述現有問題,這就意味着它的現有數據結構必需要足夠通用。或者換句話說,它提供了比OOP更高階的算法抽象。
它很靈活,每個Typeclass和DataType都是不少算法的高階抽象,相互組合能夠有無限的可能。
它的代碼組織方式也和OOP徹底不一樣

這裏淺嘗輒止的介紹了一下高階類型,更多的內容和實用還須要你們本身去理解


參考資料:
arrow
Higher kinded types for Java
Cats
Functional Programming in Scala
fpinkotlin
寫給程序猿的範疇論

相關文章
相關標籤/搜索