读者 monad 很难掌握,因为它使用的功能相当普通(函数应用)并且它的应用有点不直观。怎么能f => g => x => f(g(x)) (x)
当两个参数最初相同时有用吗?让我们从一个简单的例子开始:
常量公司 = x => x + 1;
const sqr = x => x * x;
const add = x => y => x + y;
取决于位置add
由于数量存在偏差,这些函数无法开箱即用地组合。从更广义的意义上来说,你可以说add
需要一个额外的参数并且inc
/sqr
需要了解这种情况。
Reader Monad 可用于在此类场景中获得更大的灵活性。在无类型设置中,Reader 值只是存储在普通旧 JS 对象中的函数:
const Reader = f => ({
[Symbol.toStringTag]: "Reader",
run: e => f(e) // eta expanded for clarity
});
const incR = x => Reader(e => x + 1);
const sqrR = x => Reader(e => x * x);
const addR = x => Reader(e => x + e);
初始示例中的功能现在已调整为新的 Reader“类型”。e
决定性的因素就是环境。它是由 Reader Monad 处理的隐式额外参数。e
可以是一个标量值或一个复合值来编码多个额外的参数。如你看到的e
仅由以下人员使用addR
并被其他人忽视。
这些函数如何组合呢?显然,正常的函数组合不再起作用了。我们需要一个构造来编码组合如何与 Reader 类型一起工作。这正是 monad 结构给我们带来的:
const Reader = f => ({
[Symbol.toStringTag]: "Reader",
run: e => f(e)
});
const id = x => x;
Reader.chain = mg => fm => Reader(e => fm(mg.run(e)).run(e));
Reader.of = x => Reader(_ => x);
Reader.ask = Reader(id);
const r = Reader.chain(incR(2)) (x => // Reader {run: f}
Reader.chain(sqrR(x)) (y =>
Reader.chain(addR(y)) (z =>
Reader(e => [e, z]))));
I use Reader.chain
用于组合和提供价值2
进入构图。结果r
的计算是Reader {run: f}
。这给我们提供了构图尚未评估的线索。缺了点什么。对,环境论。让我们通过它:
r.run(5) // [5, 14]
该组合产生原始环境参数e
以及计算结果。这是未纠缠的计算:
2 + 1 = 3
3 * 3 = 9
9 + 5 = 14
// ^env
Reader.chain
构建一个嵌套函数调用堆栈,这是对仅在传递环境参数时才进行计算的计算的描述。
如果我们想要怎么办sqrK
以基于e
还有?只是Reader.ask
环境:
const r2 = Reader.chain(incR(2)) (x =>
Reader.chain(Reader.ask) (e2 =>
// ^^^^^^^^^^
Reader.chain(sqrR(e2 % 2 === 1 ? 1 : x)) (y =>
Reader.chain(addR(y)) (z =>
Reader(e => [e, z])))));
r2.run(5); // [5, 6]
r2.run(4); // [4, 13]
所需要的只是一个额外的Reader.chain(Reader.ask)
call. e2
为后续的延续提供环境。
这就是它的要点。这是许多单子样板文件,以换取隐式线程参数。我想说,如果您需要传递配置并且已经使用另一个 monad 进行组合,那么它仍然很有用。不同类型的 Monad 不能直接组合,但您可以使用 monad 变压器堆栈。
这是给定示例的一个可运行示例,包括infix
平面组合语法的运算符:
const r_ = infix(
incR,
kompR,
sqrR,
kompR,
addR) (2);
Reader.chain(r_) (z => Reader(e => [e, z])).run(5);
或者,您可以使用生成器语法糖来获得更命令式的编码体验:https://stackoverflow.com/a/65060136/5536315 https://stackoverflow.com/a/65060136/5536315.
const Reader = f => ({
[Symbol.toStringTag]: "Reader",
run: e => f(e)
});
const id = x => x;
const log = x => console.log(x);
Reader.map = f => tg => Reader(e => f(tg.run(e)));
Reader.ap = tf => tg => Reader(e => tf.run(e) (tg.run(e)))
Reader.of = x => Reader(_ => x);
Reader.chain = mg => fm => Reader(e => fm(mg.run(e)).run(e));
Reader.ask = Reader(id);
const incR = x => Reader(e => x + 1);
const sqrR = x => Reader(e => x * x);
const addR = x => Reader(e => x + e);
const komp = ({chain}) => fm => gm => x => chain(fm(x)) (gm);
const kompR = komp({chain: Reader.chain});
const makeInfix = nestFirst => (...args) => {
if (args.length === 0) throw new TypeError("no argument found");
let i = 1, x = args[0];
while (i < args.length) {
if (i === 1) x = args[i++] (x) (args[i++]);
else if (nestFirst) x = args[i++] (x) (args[i++]);
else x = args[i++] (args[i++]) (x);
}
return x;
};
const infix = makeInfix(true);
const r = Reader.chain(incR(2)) (x =>
Reader.chain(sqrR(x)) (y =>
Reader.chain(addR(y)) (z =>
Reader(e => [e, z]))));
r.run(5); // [5, 14]
const r2 = Reader.chain(incR(2)) (x =>
Reader.chain(Reader.ask) (e2 =>
Reader.chain(sqrR(e2 % 2 === 1 ? 1 : x)) (y =>
Reader.chain(addR(y)) (z =>
Reader(e => [e, z])))));
r2.run(5); // [5, 6]
r2.run(4); // [4, 13]
const r_ = infix(
incR,
kompR,
sqrR,
kompR,
addR) (2);
log(Reader.chain(r_) (z => Reader(e => [e, z])).run(5)); // [5, 14]
const r2_ = infix(
incR,
kompR,
x => Reader(e => e % 2 === 1 ? 1 : x * x),
kompR,
addR) (2);
log(Reader.chain(r2_) (z => Reader(e => [e, z])).run(5)); // [5, 6]
log(Reader.chain(r2_) (z => Reader(e => [e, z])).run(4)); // [4, 13]