Kotlin函数式编程(一)
Kotlin现在也是很多人在用的语言,在谷歌宣布支持后,在国内也掀起了不小的切换到kotlin的热潮。最近开新项目我也在尝试这门语言,深深被它提供的函数式编程能力吸引住了。
这两年函数式编程有点火热,而kotlin作为一种开放式的语言,兼顾OOP与FP,它把选择权交给我们开发者,这给我们提供了很大的便利,我们可以按自己的喜好去实现工程,甚至两者都用!大家都熟悉kotlin集合提供的map,forEach等等,就是函数式编程思想在kotlin里面的一种应用,我在了解这些函数之后,也想自己实现一个List玩玩。当然啦,在实际项目中,架构设计使用OOP,底层方法实现使用FP,效果很棒。
扯得有点远了,直奔主题,今天我们来探讨一下如何以函数式编程的风格实现自己的List以及相应的forEach,map等函数。
首先实现我们的List类:
sealed class FunctionalList<out T> {
object Empty : FunctionalList<Nothing>()
data class Construct<out T>(val head: T, val tail: FunctionalList<T>) : FunctionalList<T>()
}
其中Construct是实际持有值的列表类,而Empty用于指示列表的结束。一个基本的列表类就完成了,我们接着看如何使用:
fun main(args: Array<String>) {
val numbers = Construct(1, Construct(2, Construct(3, Empty)))
}
这有点FP里函数组合的意思了,可我们不可能每次想要获得一个list都要这么麻烦吧,我们得有我们自己的listOf函数。
fun <T> listOf(vararg items: T): FunctionalList<T> {
return if (items.isEmpty()) {
Empty
} else {
Construct(items.first(), listOf(*items.copyOfRange(1, items.size)))
}
}
现在创建一个列表就简单多了!
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
}
好了,准备工作也完成了,我们来开始实现我们的forEach吧。
fun forEach(f: (T) -> Unit) {
tailrec fun go(list: FunctionalList<T>, f: (T) -> Unit) {
when (list) {
is Construct -> {
f(list.head)
go(list.tail, f)
}
is Empty -> Unit
}
}
go(this, f)
}
我们这里使用递归来实现。不停地去获取Construct对象的head并应用我们传入的函数f,直到我们遇到Empty。这里tailrec
是kotlin支持的函数式编程的尾递归
,这允许我们常用的循环来写的算法改用递归来实现,编译器还会帮我们优化该递归,且没有堆栈溢出的风险。关于tailrec
详情见kotlin tailrec的说明
调用如下:
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
list.forEach {
println(it)
}
}
也很简单对吧?
接下来我们来实现fold函数:
fun <R> fold(init: R, f: (R, T) -> R): R {
tailrec fun go(list: FunctionalList<T>, init: R, f: (R, T) -> R): R = when (list) {
is Construct -> go(list.tail, f(init, list.head), f)
is Empty -> init
}
return go(this, init, f)
}
我们可以发现代码也跟forEach类似。不过有了fold后我们实现reverse就很简单了。
fun reverse(): FunctionalList<T> = fold(Empty as FunctionalList<T>) { acc, i -> Construct(i, acc) }
嘿嘿嘿,我有个大胆的想法,那我reverse方法再加个fold不就是foldRight了?
fun <R> foldRight(init: R, f: (R, T) -> R): R {
return this.reverse().fold(init, f)
}
有了foldRight,我们实现map也简单多了。
fun <R> map(f: (T) -> R): FunctionalList<R> {
return foldRight(Empty as FunctionalList<R>) { tail, head -> Construct(f(head), tail) }
}
哇,回头看看,我们为了实现这些函数真是做了不少骚操作呢,注意我们这里的实现并没有考虑性能,我们可以跟原生list跑一把:
fun main(args: Array<String>) {
val funList: FunctionalList<Int> = listOf(1, 2, 3, 4)
val list: List<Int> = mutableListOf(1, 2, 3, 4).toList()
println("fold on functionalList : ${executionTime { funList.fold(0) { acc, i -> acc + i } }}")
println("fold on list : ${executionTime { list.fold(0) { acc, i -> acc + i } }}")
}
哈哈哈,结局感人,其他的方法我就不比了,你们可以试试。

这是因为kotlin内部实现是基于命令式编程做了大量的优化,我们的List还有很大的优化空间。而且kotlin提供的诸如forEach之类的方法内部都是基于for循环实现的,那我们在实际使用list的时候一定要注意不能串联太多的函数,那样的性能不如我们在一个for循环或者forEach函数里执行多项操作来的高。
好了,今天我们对于使用kotlin进行函数式编程的尝试就到这里了,你们学到了吗?下次,我们再看看一些其他的玩法,期待吗?
源码地址:FuctionalListSample
