对于Scala函数式编程的理解
函数式编程思想
- 把计算过程看作是函数的组合和应用,而不是指令的执行。
- 把函数看作是一等公民,可以作为参数、返回值或数据结构。
- 避免使用可变对象和副作用,保持数据和行为的纯粹性。
- 使用高阶函数、递归、模式匹配等技巧来简化和抽象代码。
- 使用惰性求值、尾递归优化等方法来提高性能和内存效率。
使用可变对象的副作用
假设我们有一个可变的列表,里面存放了一些数字:
1 | var numbers = List(1, 2, 3, 4) |
然后我们定义了一个函数,它接受一个列表和一个函数作为参数,返回一个新的列表,其中每个元素都是用函数处理过的:
1 | def map(list: List[Int], f: Int => Int): List[Int] = { |
这个函数看起来没有问题,但是如果我们传入的函数有副作用呢?比如这样一个函数,它会修改原来的列表:
1 | def addOneAndChangeList(n: Int): Int = { |
那么我们调用map(numbers, addOneAndChangeList)
时会发生什么呢?我们期望得到一个新的列表List(2, 3, 4, 5)
,但实际上我们得到的是List(2)
。为什么呢?因为每次调用addOneAndChangeList
时,都会修改原来的numbers
列表,导致循环中只能处理第一个元素。这就是使用可变对象导致的错误。
如果我们使用不可变对象,就不会有这样的问题。比如这样定义一个不可变的列表:
1 | val numbers = List(1, 2, 3, 4) |
然后使用同样的map
函数和addOneAndChangeList
函数,我们就会得到正确的结果:List(2, 3, 4, 5)
。因为不可变对象不能被修改,所以每次调用addOneAndChangeList
时都不会影响原来的列表。
为什么无副作用
无副作用是因为一个函数只是根据传入的不可变的对象,返回一个结果,别的什么都没做
Scala函数式编程的特性
高阶函数
指可以接受其他函数作为参数或返回其他函数作为结果的函数。高阶函数可以抽象出一些通用的逻辑,让代码更简洁和灵活。
例子:
1 | def map(list: List[Int], f: Int => Int): List[Int] = { |
这个函数接受一个函数f和一个列表list,然后对列表中的每个元素应用f,并返回一个新的列表。
匿名函数
指没有名字的函数,通常用于定义一些简单的逻辑或作为高阶函数的参数。匿名函数可以避免命名冲突和污染全局作用域。
例子:
1 | val addOne = (x: Int) => x + 1 |
这个函数没有名字,只有参数和返回值,它可以作为一个值赋给变量或传递给其他函数。
柯里化
指把一个接受多个参数的函数转换成一个接受单一参数并返回一个新函数的过程。柯里化可以实现部分应用和延迟计算,以及提高代码的可读性和复用性。
例子:
1 | def add(x: Int)(y: Int) = x + y |
这个函数接受两个参数x和y,但是它可以分两次调用,例如 val addTwo = add(2)_
这样就得到了一个新的函数addTwo,它可以接受一个参数并返回结果。
模式匹配
指根据数据的结构或值来选择不同的分支执行不同的操作。模式匹配可以简化复杂的条件判断和数据解构,让代码更清晰和优雅。
例子:
1 | def factorial(n: Int): Int = n match { |
这个函数根据n的值来选择不同的分支执行,如果n是0,就返回1,否则就返回n乘以n-1的阶乘。
惰性求值
指只在需要时才计算表达式的值,而不是立即计算。惰性求值可以避免不必要的计算,提高程序的性能和内存效率。
例子:
1 | lazy val fibs: Stream[Int] = 0 #:: 1 #:: (fibs zip fibs.tail).map{ case (a,b) => a + b } |
这个表达式定义了一个无穷数列fibs,它表示斐波那契数列。但是这个数列并不会立即计算所有的元素,而是只在需要时才计算下一个元素。