函数式编程-纯函数
2019-10-15 13:43:4510/1/2021, 3:34:43 AM
纯函数的基本概念
纯函数是这样一种函数,相同的输入永远会得到相同的输出,而且没有任何可观察的副作用。
可能会导致不纯的情况:
修改调用/传入的参数
let array = [1, 2, 3, 4, 5]; // 纯的 array.slice(0, 3); //[1, 2, 3] array.slice(0, 3); //[1, 2, 3] // 不纯的 --> 会修改array array.splice(0, 3); //[1, 2, 3] array.splice(0, 3); //[4, 5]
依赖外部环境
// 纯的 function checkAge(minmun, age) { return age > minmun; } // 不纯的 --> 引入了外部的环境,从而增加了认知负荷(cognitive load) let minmum = 21; function checkAge(age) { return age > minmun; }
副作用
副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。
副作用可能包括:
- 更改文件系统
- 往数据库插入记录
- 发送一个 http 请求
- 修改传入的数据
- 打印/log
- 获取用户输入
- DOM 查询
- 访问系统状态
- ......
追求纯的理由
可缓存性(cacheable)
function memoize(f) { let cache = {}; return function() { let arg_str = JSON.stringify(arguments); console.log(cache[arg_str]); cache[arg_str] = cache[arg_str] || f.apply(f, arguments); return cache[arg_str]; }; } const squareNumber = memoize(x => x * x); squareNumber(3); squareNumber(3);
可移植性/自文档化(portable/self-documenting)
可测试性(testable)
合理性(reasonable)
使用纯函数的最大好处是引用透明性(referential transparency)。
如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么就认为这段代码是引用透明的。由于纯函数总是能够根据相同的输入返回相同的输出,所以它们就能够保证总是返回同一个结果,这也就保证了引用透明性。对于引用透明的代码,可以使用"等式推导"(equational reasoning)的技术来分析代码,所谓"等式推导"就是"一对一"替换,有点像在不考虑程序行执行的怪异行为(quirks of programmatic evaluation)的情况下手动执行相关代码。
let Immutable = require("immutable"); // decrementHP是纯函数!!!因为player的来源是immutable.Map,set方法不会使map改变,而是返回一个新的map对象 function decrementHP(player) { return player.set("hp", player.hp - 1); } function isSameTeam(player1, player2) { return player1.team === player2.team; } function punch(player, target) { if (isSameTeam(player, target)) { return target; } else { return decrementHP(target); } } let jobe = Immutable.Map({ name: "Jobe", hp: 20, team: "red" }); let michael = Immutable.Map({ name: "Michael", hp: 20, team: "green" }); punch(jobe, michael); //=> Immutable.Map({name:"Michael", hp:19, team: "green"})
并行代码
可以并行运行任意纯函数,因为纯函数根本不需要访问共享内存,而且根据其定义,纯函数也不会因副作用而进入竞态(race condition)