跳到主要内容

自由单子与单子变换器

自由单子怎样分离程序描述与执行?​

  • 自由单子: 从任意函子构造单子,不加入领域执行语义;
  • Pure: 保存最终值并结束计算;
  • Free: 保存包含下一步的指令函子;
  • 程序: 形成命令抽象语法树;解释器决定命令如何执行;
const Pure = (value) => ({
pure: true,
value,
map: (fn) => Pure(fn(value)),
chain: (fn) => fn(value),
});

const Free = (instruction) => ({
pure: false,
instruction,
map: (fn) => Free(instruction.map((next) => next.map(fn))),
chain: (fn) => Free(instruction.map((next) => next.chain(fn))),
});

const liftF = (instruction) => Free(instruction.map(Pure));

指令函子和解释器怎样配合?​

const Log = (message, next) => ({
type: "log",
message,
next,
map: (fn) => Log(message, fn(next)),
});

const interpret = (program) => {
if (program.pure) return program.value;
const { message, next } = program.instruction;
console.log(message);
return interpret(next);
};
  • 指令只描述“要记录什么”;解释器决定使用控制台、文件或测试替身;

为什么需要单子变换器?​

  • 问题: 函子与应用函子可自然组合,任意单子通常不能直接组合;
  • 单子变换器: 接收现有单子,产生叠加新能力的单子构造器;
  • 命名: 常以 T 结尾,如 MaybeT、ReaderT、StateT;

MaybeT 怎样给外层单子增加可选性?​

const MaybeT = (M) => {
const wrap = (run) => ({
run,
map: (fn) =>
wrap(run.map((opt) => (opt.isSome ? { isSome: true, value: fn(opt.value) } : opt))),
chain: (fn) => wrap(run.chain((opt) => (opt.isSome ? fn(opt.value).run : M.of(opt)))),
});
return {
of: (value) => wrap(M.of({ isSome: true, value })),
none: () => wrap(M.of({ isSome: false })),
};
};
  • 结果: 外层单子的顺序能力与 Maybe 的短路能力由同一个 chain 组合;