到底什么是函数式编程?
函数式编程在数学理论上的定义很复杂,而对于 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 种代码,它们之间的可读性也有很大的差异。