跳到主要内容

偏函数与全函数

偏函数为什么难以推理?​

  • 偏函数: 没有为类型声明范围内的全部输入定义有效结果;
  • 失败形态: 抛错、返回意外值,或永不终止;
  • 代价: 调用方必须记住隐含边界,遗漏一次检查就可能形成运行时错误;
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: 不携带值,跳过后续映射;
  • 提示作用: 返回类型直接告诉调用方,结果可能不存在;

全函数提供什么保证?​

  • 全函数: 对其类型所允许的每个输入,都返回一个有效结果;
  • 对照: 偏函数可能抛错、给出意外结果或无法终止;
  • 收益: 边界行为成为实现的一部分,代码更容易组合、维护和推理;