Kotlin代码日记Kotlin编程Android开发

Kotlin函数式编程(一)

2018-08-03  本文已影响113人  小小小小小粽子

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


关注一下呗
上一篇 下一篇

猜你喜欢

热点阅读