函数式编程举例

78 阅读3分钟

到底什么是函数式编程?

函数式编程在数学理论上的定义很复杂,而对于 Kotlin 函数式编程来说,其实我们需要记住两个重点:

1、函数是一等公民;

2、纯函数。

而以这两个点作为延伸,我们就可以扩展出很多函数式编程的其他概念。比如说,函数是一等公民,这就意味着:

函数可以独立于类之外,这就是 Kotlin 的顶层函数;

函数可以作为参数和返回值,这就是高阶函数和 Lambda;

函数可以像变量一样,这就是函数的引用;

当函数的功能更加强大以后,我们就可以几乎可以做到:只使用函数来解决所有编程的问题。

再比如,对于纯函数的理解,这就意味着:

函数不应该有副作用。所谓副作用,就是“对函数作用域以外的数据进行修改”,而这就引出了函数式的不变性。在函数式编程当中,我们不应该修改任何变量,当我们需要修改变量的时候,我们要创建一份新的拷贝再做修改,然后再使用它(这里,你是不是马上就想到了数据类的 copy 方法呢?)。

无副作用的函数,它具有幂等性,换句话说就是:函数调用一次和调用 N 次,它们的效果是等价的。

无副作用的函数,它具有引用透明的特性。

无副作用的函数,它具有无状态的特性。

举例:

for 循环,是命令式编程当中最典型的语句。举个例子,从 1 到 10 的总和,使用 for 循环:

fun loop(): Int {
    var result = 0
    for (i in 1..10) {
        result += i
    }

    return result
}

如果不使用 for 循环,仅仅只使用函数,那就是递归。

fun recursionLoop(): Int {
    fun go(i: Int, sum: Int): Int =
        if (i > 10) sum else go(i + 1, sum + i)

    return go(1, 0)
}

递归都是有调用栈开销的,所以我们应该尽量使用尾递归。对于这种类型的递归,在经过栈复用优化以后,它的开销就可以忽略不计了,我们可以认为它的空间复杂度是 O(1)。

fun recursionLoop(): Int {
// 变化在这里
//     ↓
    tailrec fun go(i: Int, sum: Int): Int =
        if (i > 10) sum else go(i + 1, sum + i)

    return go(1, 0)
}

上面的递归思路只是为了说明我们可以用它替代循环。在实际的开发工作中,这种方式是不推荐的,毕竟它太绕了,对吧?如果要在工作中实现类似的需求,我们使用 Kotlin 集合操作符一行代码就能搞定:

fun reduce() = (1..10).reduce { acc, i -> acc + i } // 结果 55

Kotlin 还为我们提供了另一个更简单的操作符,也就是 sum:

fun sum() = (1..10).sum() // 结果 55

使用 Kotlin,我们运用不同的思维,可以写出截然不同的 4 种代码。而即使同样都是函数式的思想的 3 种代码,它们之间的可读性也有很大的差异。