javascript - 有没有更好的方法来模仿 JS 中的 do 表示法?

标签 javascript functional-programming monads do-notation

一元计算在 JS 中很快变得令人困惑:

const chain = fm => xs =>
  xs.reduce((acc, x) => acc.concat(fm(x)), []);

const of = x => [x];

const main = xs => ys => zs =>
  chain(x =>
    x === 0
      ? []
      : chain(y =>
          chain(z => [[x, y, z]]) (zs)) (ys)) (xs);

console.log("run to completion",
  main([1, 2]) (["a", "b"]) ([true, false]));
  
console.log("short circuiting",
  main([0, 2]) (["a", "b"]) ([true, false]));


在 Haskell do表示法可用于隐藏嵌套函数调用。但是,do是 Javascript 缺乏的一种编译时技术。

生成器函数似乎很合适,但它们不适用于提供优先选择的 monad。所以我一直在寻找一个替代方案,最近想出了一种单子(monad)应用程序,以便稍微解开嵌套计算:

const chain = fm => xs =>
  xs.reduce((acc, x) => acc.concat(fm(x)), []);

const of = x => [x];

const id = x => x;

const infixM3 = (w, f, x, g, y, h, z) =>
  f(x_ =>
    w(x_, w_ => g(y_ =>
      w_(y_, w__ => h(z_ =>
        w__(z_, id)) (z))) (y))) (x);

const mainApp = xs => ys => zs => infixM3(
  (x, k) =>
    x === 0
      ? []
      : k((y, k) =>
          k((z, k) => [[x, y, z]])),
  chain, xs,
  chain, ys,
  chain, zs);

console.log("run to completion",
  mainApp([1, 2]) (["a", "b"]) ([true, false]));

console.log("short circuiting",
  mainApp([0, 2]) (["a", "b"]) ([true, false]));


涂抹器在中缀位置模仿链条,因此得名。它基于局部延续,因此提升函数需要一对分别由边界值和延续组成的参数。如果未应用延续,则计算短路。它看起来很复杂,但实际上是一个机械过程。

将显式版本与抽象版本进行比较,我认为这是在可读性方面的改进:
chain(x =>
  x === 0
    ? []
    : chain(y =>
        chain(z => [[x, y, z]]) (zs)) (ys)) (xs);

infixM3(
  (x, k) =>
    x === 0
      ? []
      : k((y, k) =>
          k((z, k) => [[x, y, z]])),
  chain, xs,
  chain, ys,
  chain, zs);

提升功能中的延续以及涂抹器具有数量意识的事实令人困扰。此外,它看起来根本不像 do-notation。我们能否更接近类似于 do 的语法?符号?

最佳答案

您可以使用 immutagen使用生成器编写一元代码的库。

const monad = bind => regen => (...args) => function loop({ value, next }) {
    return next ? bind(value, val => loop(next(val))) : value;
}(immutagen.default(regen)(...args));

const flatMap = (array, callback) => array.flatMap(callback);

const list = monad(flatMap);

const main = list(function* (xs, ys, zs) {
    const x = yield xs;
    if (x === 0) return [];
    const y = yield ys;
    const z = yield zs;
    return [[x, y, z]];
});

console.log("run to completion", main([1, 2], ["a", "b"], [true, false]));

console.log("short circuiting", main([0, 2], ["a", "b"], [true, false]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/immutagen@1.0.9/immutagen.js"></script>


如您所见,这也适用于非确定性 monad。但是,它有两个缺点。
  • 这是低效的,因为需要创建和重放多个生成器,导致时间复杂度呈二次增长。
  • 它仅适用于纯 monad 和纯计算,因为需要创建和重放多个生成器。因此,副作用将被错误地执行多次。

  • 尽管如此,即使您不使用生成器,在 JavaScript 中编写单子(monad)代码也不是那么糟糕。

    const flatMap = (array, callback) => array.flatMap(callback);
    
    const main = (xs, ys, zs) =>
        flatMap(xs, x =>
        x === 0 ? [] :
        flatMap(ys, y =>
        flatMap(zs, z =>
        [[x, y, z]])));
    
    console.log("run to completion", main([1, 2], ["a", "b"], [true, false]));
    
    console.log("short circuiting", main([0, 2], ["a", "b"], [true, false]));
    .as-console-wrapper { max-height: 100% !important; top: 0; }


    归根结底,如果你想要两全其美,那么你要么需要使用编译为 JavaScript 的语言,要么使用 Babel 或 sweet.js 之类的预处理器。 .

    关于javascript - 有没有更好的方法来模仿 JS 中的 do 表示法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60226639/

    相关文章:

    javascript - nginx 在更改时提供错误的 js 文件

    javascript - 将有状态组件转换为无状态组件

    javascript - 如何通过spring websocket向特定设备发送消息

    functional-programming - lambda 演算中列表元素的总和和列表长度

    scala - 以无状态方式处理输入事件

    haskell - 状态 Monad 的范围

    javascript - 从 JPG 切换到 YouTube 视频

    functional-programming - 建模/记录功能程序

    haskell - 有人看过棘手的 Haskell 练习列表吗?

    haskell - Haskell 中的半独立 Action