偏函数与全函数
偏函数为什么难以推理?
- 偏函数: 没有为类型声明范围内的全部输入定义有效结果;
- 失败形态: 抛错、返回意外值,或永不终止;
- 代价: 调用方必须记住隐含边界,遗漏一次检查就可能形成运行时错误;
const sum = (xs) => xs.reduce((a, b) => a + b);
sum([]); // TypeError:没有初始值
const first = (xs) => xs[0];
first([]); // undefined
const times = (n) => (fn) => n && (fn(n), times(n - 1)(fn));
times(-1)(console.log); // 最终栈溢出
怎样把偏函数改成全函数?
- 默认值: 为归约提供单位元,使空输入也有合法结果;
- 守卫: 明确限制递归继续的条件,保证终止;
- 显式类型: 用
Option表示“可能没有值”,把检查纳入返回类型;
const sum = (xs) => xs.reduce((a, b) => a + b, 0);
const first = (xs) => (xs.length ? Some(xs[0]) : None());
const times = (n) => (fn) => n > 0 && (fn(n), times(n - 1)(fn));
Option 如何消除遗漏检查?
first([[42]]).map((a) => console.log(a[0])); // 42
first([]).map((a) => console.log(a[0])); // 不执行回调
Some: 携带存在的值,允许后续映射;None: 不携带值,跳过后续映射;- 提示作用: 返回类型直接告诉调用方,结果可能不存在;
全函数提供什么保证?
- 全函数: 对其类型所允许的每个输入,都返回一个有效结果;
- 对照: 偏函数可能抛错、给出意外结果或无法终止;
- 收益: 边界行为成为实现的一部分,代码更容易组合、维护和推理;