高阶类型带来了什么

自从实用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
写给程序猿的范畴论

相关文章
相关标签/搜索