本文实例是由Ramda编写
# 什么是函数式编程?
形式:数据可以不断的从一个函数的输出可以流入另一个函数输入
函数式编程的思维过程着眼点是函数,而不是过程,它强调的是如何通过函数的组合变换去解决问题,而不是我通过写什么样的语句去解决问题,当你的代码越来越多的时候,这种函数的拆分和组合就会产生出强大的力量。
在我们的编程世界中,我们需要处理的其实也只有“数据”和“关系”,而关系就是函数。我们所谓的编程工作也不过就是在找一种映射关系。
# 函数式编程的特点?
- 函数是“一等公民”
- 声明式编程 (Declarative Programming)
- 惰性执行:函数只在需要的时候执行,即不产生无意义的中间变量
- 无状态和数据不可变 (Statelessness and Immutable data)
- 没有副作用(No Side Effects)
- 纯函数 (pure functions)
# 纯函数的好处?
- 便于测试和优化:由于纯函数对于相同的输入永远会返回相同的结果,因此我们可以轻松断言函数的执行结果,同时也可以保证函数的优化不会影响其他代码的执行。这十分符合测试驱动开发 TDD(Test-Driven Development) 的思想,这样产生的代码往往健壮性更强。
- 可缓存性:因为相同的输入总是可以返回相同的输出,因此,我们可以提前缓存函数的执行结果
- 自文档化:由于纯函数没有副作用,所以其依赖很明确,因此更易于观察和理解
- 更少的 Bug:使用纯函数意味着你的函数中不存在指向不明的 this,不存在对全局变量的引用,不存在对参数的修改,这些共享状态往往是绝大多数 bug 的源头。
# 柯里化(Currying)
柯里化的意思是将一个多元函数,转换成一个依次调用的单元函数。
f(a,b,c) → f(a)(b)(c)
为什么单元函数很重要?如果我们想顺利的组装流水线,那我就必须保证我每个加工站的输出刚好能流向下个工作站的输入。因此,在流水线上的加工站必须都是单元函数。
# 高级柯里化
我们使用的 Lodash,Ramda 这些库中实现的 curry 函数的行为好像和柯里化不太一样呢? 其实,这些库中的 curry 函数都做了很多优化,导致这些库中实现的柯里化其实不是纯粹的柯里化,我们可以把他们理解为“高级柯里化”。这些版本实现可以根据你输入的参数个数,返回一个柯里化函数或者结果值。即,如果你给的参数个数满足了函数条件,则返回值。
const add = R.curry((x, y, z) => x + y + z);
const add7 = add(7);
add(7)(1) // function
# 函数组合
函数组合的目的是将多个函数组合成一个函数。下面来看一个简化版的实现:
const compose = (f, g) => x => f(g(x))
const f = x => x + 1;
const g = x => x * 2;
const fg = compose(f, g);
fg(1) //3
我们可以看到 compose 就实现了一个简单的功能:形成了一个全新的函数,而这个函数就是一条从 g -> f 的流水线。同时我们可以很轻易的发现 compose 其实是满足结合律的,只要其顺序一致,最后的结果是一致的
compose(f, compose(g, t)) = compose(compose(f, g), t) = f(g(t(x)))
# 实践经验
# 柯里化中把要操作的数据放到最后
因为我们的输出通常是需要操作的数据,这样当我们固定了之前的参数(我们可以称为配置)后,可以变成一个单元函数,直接被函数组合使用,这也是其他的函数式语言遵循的规范:
const split = curry((x, str) => str.split(x));
const join = curry((x, arr) => arr.join(x));
const replaceSpaceWithComma = compose(join(','), split(' '));
const replaceCommaWithDash = compose(join('-'), split(','));
但是如果有些函数没遵循这个约定,我们的函数该如何组合?当然也不是没办法,很多库都提供了占位符的概念,例如 Ramda 提供了一个占位符号(R.__)。假设我们的 split 把 str 放在首位
const split = curry((str, x) => str.split(x));
const replaceSpaceWithComma = compose(join(','), split(R.__, ' '));
# 函数组合中函数要求单输入
函数组合有个使用要点,就是中间的函数一定是单输入的,这个很好理解,之前也说过了,因为函数的输出都是单个的(数组也只是一个元素)。
# 函数式编程的优点
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于"并发编程"
- 更少的出错概率
# 函数式编程的缺点
- 性能
- 资源占用
- 递归陷阱
# 总结
学习函数式编程真正的意义在于:让你意识到在指令式编程,面向对象编程之外,还有一种全新的编程思路,一种用函数的角度去抽象问题的思路。
我们完全可以在日常工作中将函数式编程作为一种辅助手段,在条件允许的前提下,借鉴函数式编程中的思路,例如:
- 多使用纯函数减少副作用的影响。
- 使用柯里化增加函数适用率。
- 使用 Pointfree 编程风格,减少无意义的中间变量,让代码更且可读性。