函数式编程浅谈——函数
函数式编程是一种思想,可能你一直在沿用这种思想,但是,你能说清楚,它到底是什么吗?
瞧一瞧~
目录
前言
中心思想: 为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。
一、一等公民的函数
当我们说函数是“一等公民”的时候,实际上说的是它们和其他对象都一样,你可以像对待任何其他数据类型一样对待它们——把它们存在数组里,当作参数传递,赋值给变量…等等。
先来一个例子预热一下:
const hi = name => `Hi ${name}`;
const greeting = name => hi(name); //感觉我也写过...
greeting 只不过是转了个身然后以相同的参数调用了 hi 函数而已,因此我们可以这么写:
const greeting = hi;
greeting("times"); // "Hi times"
用一个函数把另一个函数包起来,目的仅仅是延迟执行,真的是非常糟糕的编程习惯。(这可维护性密切相关。)
进阶:
const getServerStuff = callback => ajaxCall(json => callback(json));
// 等价于
const getServerStuff = ajaxCall;
以下是上述两种写法等价的原因:(类似数学换算)
原代码 | 新代码 |
---|---|
ajaxCall(json => callback(json)); | ajaxCall(callback); |
getServerStuff = callback => ajaxCall(callback); | getServerStuff = ajaxCall |
各位,以上才是写函数的正确方式。看到这里,终于有点开窍了吧。
const BlogController = {
index(posts) { return Views.index(posts); },
show(post) { return Views.show(post); },
create(attrs) { return Db.create(attrs); },
update(post, attrs) { return Db.update(post, attrs); },
destroy(post) { return Db.destroy(post); },
};
我们可以把它重写成这样:
const BlogController = {
index: Views.index,
show: Views.show,
create: Db.create,
update: Db.update,
destroy: Db.destroy,
};
为何钟爱一等公民?
- 每次改动将会少得多:
- 有利于剥离相同的概念
项目中常见的一种造成混淆的原因是,针对同一个概念使用不同的命名。还有通用代码的问题。比如,下面这两个函数做的事情一模一样,但后一个就显得更加通用,可重用性也更高:
const validArticles = articles =>
articles.filter(article => article !== null && article !== undefined),
// 对未来的项目更友好
const compact = xs => xs.filter(x => x !== null && x !== undefined);
在命名的时候,我们特别容易把自己限定在特定的数据上(本例中是 articles)。这种现象很常见,也是重复造轮子的一大原因。
二、纯(干净)的函数
首先,我们要理清纯函数的概念。
纯函数是这样一种函数:
- 即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
比如 slice 和 splice,这两个函数的作用类似
- slice 符合:因为对相同的输入它保证能返回相同的输出。
- splice 不符合:因为被处理的数组永久地改变了。
让我们来仔细研究一下“副作用”以便加深理解
- 副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。
- 副作用可能包含,但不限于:
- 更改文件系统
- 往数据库插入记录
- 发送一个 http 请求
- 可变数据
- 打印/log
- 获取用户输入
- DOM 查询
- …
为何要坚持这种「相同输入得到相同输出」原则
函数是不同数值之间的特殊关系:每一个输入值返回且只返回一个输出值。
换句话说,函数只是两种数值之间的关系:输入和输出。尽管每个输入都只会有一个输出,但不同的输入却可以有相同的输出。下图展示了一个合法的从x
到y
的函数关系;
相反,下面这张图表展示的就不是一种函数关系,因为输入值 5 指向了多个输出:
纯函数就是数学上的函数,而且是函数式编程的全部。使用这些纯函数编程能够带来大量的好处,让我们来看一下为何要不遗余力地保留函数的纯粹性的原因。
我们已经大致了解什么是纯函数了,也看到作为函数式程序员的我们,为何深信纯函数是不同凡响的。从这开始,我们将尽力以纯函数式的方式书写所有的函数。为此我们将需要一些额外的工具(curry)来达成目标,同时也尽量把非纯函数从纯函数代码中分离。