本系列文章基于阅读《Scala 函数式编程》的笔记和心得。

概述

函数式编程(FP) 基于一个前提:

只用纯函数来构造程序。

换句话说,函数没有副作用副作用包括但不限于:

  • 修改一个变量
  • 直接修改数据结构
  • 设置一个对象的成员
  • 抛出一个异常或者错误
  • 打印到终端或读取用户的输入
  • 读取或写入一个文件
  • 在屏幕上绘画

函数式编程限制的是怎样写程序,而非表达什么样的程序。

函数式编程的好处

副作用导致代码很难测试。

应该通过把副作用推到程序的外层,来转换任何带有副作用的函数。

对函数式编程而言,程序的实现,应该有一个纯的内核和一层很薄的外围来处理副作用

纯函数更容易推理。

一个函数在程序的执行过程中除了根据输入参数给出运算结果之外没有其他的影响,就可以说没有副作用。有时也称为纯函数

对于程序 p ,如果它包含的表达式 e 满足引用透明,所有的 e 都可以替换为它的运算结果而不改变程序 p 的含义。假设存在一个函数 f,若表达式 f(x) 对所有引用透明的表达式 x 也是引用透明的,那么这个 f 就是一个纯函数。

引用透明要求函数不论进行了任何操作都可以用它的返回值来代替。这种限制使得推倒一个程序的求值变得简单而自然,称之为代换模型

高阶函数

函数也是值,就像其他类型的值,比如整型,字符串,列表;韩束也可以赋值给一个变量、存储在一个数据结构、当做参数传递给另一个函数。

多态函数

参数化多态,一个函数适用于多种类型的参数。

也称为泛型函数

1
def funcName[A](arg: Int, arg1: A => Boolean): Boolean

匿名函数

语法形如

1
(x: Int) => x == 42

是一段 函数字面量 或者 匿名函数。不必先定义一个有名称的函数,可以在调用时再定义。

函数也是值

当我们定义一个函数字面量的时候,实际定义了一个包含一个 apply 方法的 Scala 对象。

在 Scala 中,一个有 apply 方法的对象可以把它当成方法一样调用

我们定义的一个函数字面量 (a, b) => a < b,它其实是一段创建函数对象的语法糖:

1
2
3
val lessThan = new Function2[Int, Int, Boolean] {
def apply(a: Int, b: Int) = a < b
}

当我们以 lessThan(10, 20) 的方式调用时,实际上是对 apply 方法调用的语法糖:

1
2
scala> val b = lessThan.apply(10, 20)
b: Boolean = true

通过类型来实现多态

针对某种类型 A 的多态函数,唯一可以对 A 进行操作的方式是传入一个函数参数(或者招给出的操作来定义一个函数)。

几个例子:

  1. 部分应用函数

    1
    2
    3
    4
    5
    6
    7
    def partial1[A,B,C](a: A, f: (A, B) => C): B => C = 
    /**
    b 是从哪里来的?
    实际上,这只是一个函数字面量的参数定义,表示:
    返回一个函数,这个函数接收一个类型为 B 的参数值 b。
    */
    (b: B) => f(a, b)
  2. 柯里化

    1
    2
    def curry[A,B,C](f: (A, B)=> C): A => (B => C) = 
    (a: A) => (b: B) => f(a, b)
  3. 反柯里化

    1
    2
    def uncurry[A,B,C](f: A=>B=>C): (A,B)=>C =
    (a: A, b: B) => f(a)(b)
  4. 函数组合

    1
    2
    def compose[A,B,C](f: B=>C, g: A=>B): A=>C =
    (a: A) => f(g(a))