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