如何将功能组合内部的突变转换为局部的,不可观察的突变?

时间:2019-05-22 13:27:54

标签: javascript functional-programming

Task是一种monadic类型,表示顺序执行的异步计算。与Promise一样,有一个类似于Promise.all的组合器,但它的运行顺序如前所述:

const TYPE = Symbol.toStringTag;

const struct = type => cons => {
  const f = x => ({
    ["run" + type]: x,
    [TYPE]: type,
  });

  return cons(f);
};

const Task = struct("Task") (Task => k => Task((res, rej) => k(res, rej)));

const arrFold = alg => zero => xs => {
  let acc = zero;

  for (let i = 0; i < xs.length; i++)
    acc = alg(acc) (xs[i]);

  return acc;
};

const tMap = f => tg =>
  Task((res, rej) => tg.runTask(x => res(f(x)), rej));

const tOf = x => Task((res, rej) => res(x));

const delay = (ms, x) =>
  Task(f => setTimeout(f, ms, x), f => f(x));

const tAnd = tf => tg =>
  Task((res, rej) =>
    tf.runTask(f =>
      tg.runTask(g =>
        res([f, g]), rej),
        rej));

const tAll =
  arrFold(acc => tf =>
    tMap(([xs, x]) =>
      (xs.push(x), xs)) // A
        (tAnd(acc) (tf)))
          (tOf([]));

const main = tAll([
  delay(200, 'a'),
  delay(500, 'b'),
  delay(100, 'c')]);
  
const main2 = tAll([
  delay(100, 'd')]);

main.runTask(console.log, console.error); // ["d"]
main2.runTask(console.log, console.error); // ["d", "a", "b", "c"]

A行中的突变显然会引起副作用。我可以通过用Array.prototype.concat代替破坏性推送来避免这种情况。

但是,concat效率低下。说我必须编辑1,000,000个文件。好吧,您可以说这会很慢,因为每个文件都是按顺序处理的。但是我敢打赌,这个问题也会在其他情况下出现。

有没有办法将这种突变转化为局部的,不可观察的突变?

顺便说一句,我知道持久性数据结构将使我能够更有效地使用concat,但我想在Javascript中避免使用它们。

1 个答案:

答案 0 :(得分:2)

您已将arrFold定义为咖喱函数。然后,您可以使用它来定义tAll,方法是将其传递给三个必需参数中的两个:

const arrFold = alg => zero => xs => { /* ... */ }
const tAll = arrFold
  (acc => tf => tMap(([xs, x]) => (xs.push(x), xs)) (tAnd(acc) (tf)))
  (tOf([]));

在这里,您基本上将数组实例烘烤到tAll函数中,以便在您使用它折叠任务数组时用作zero

我可以想到的两种解决方案是(1)要么使arrFold使用“惰性” zero参数:

const arrFold = alg => zero => xs => { 
  let acc = zero();
  /* ... */
}

const tAll = arrFold
  (/* ... */)
  (() => tOf([]))

const TYPE = Symbol.toStringTag;

const struct = type => cons => {
  const f = x => ({
    ["run" + type]: x,
    [TYPE]: type,
  });

  return cons(f);
};

const Task = struct("Task") (Task => k => Task((res, rej) => k(res, rej)));

const arrFold = alg => zero => xs => {
  let acc = zero();

  for (let i = 0; i < xs.length; i++)
    acc = alg(acc) (xs[i]);

  return acc;
};

const tMap = f => tg =>
  Task((res, rej) => tg.runTask(x => res(f(x)), rej));

const tOf = x => Task((res, rej) => res(x));

const delay = (ms, x) =>
  Task(f => setTimeout(f, ms, x), f => f(x));

const tAnd = tf => tg =>
  Task((res, rej) =>
    tf.runTask(f =>
      tg.runTask(g =>
        res([f, g]), rej),
        rej));

const tAll =
  arrFold(acc => tf =>
    tMap(([xs, x]) =>
      (xs.push(x), xs)) // A
        (tAnd(acc) (tf)))
          (() => tOf([]));

const main = tAll([
  delay(200, 'a'),
  delay(500, 'b'),
  delay(100, 'c')]);
  
const main2 = tAll([
  delay(100, 'd')]);

main.runTask(console.log, console.error); // ["d"]
main2.runTask(console.log, console.error); // ["d", "a", "b", "c"]

或者,(2),每当您调用tAll时都要创建一个新的zero自变量:

const tAll = tasks => arrFold
  (/* ... */)
  (tOf([]))
  (tasks)

const TYPE = Symbol.toStringTag;

const struct = type => cons => {
  const f = x => ({
    ["run" + type]: x,
    [TYPE]: type,
  });

  return cons(f);
};

const Task = struct("Task") (Task => k => Task((res, rej) => k(res, rej)));

const arrFold = alg => zero => xs => {
  let acc = zero;

  for (let i = 0; i < xs.length; i++)
    acc = alg(acc) (xs[i]);

  return acc;
};

const tMap = f => tg =>
  Task((res, rej) => tg.runTask(x => res(f(x)), rej));

const tOf = x => Task((res, rej) => res(x));

const delay = (ms, x) =>
  Task(f => setTimeout(f, ms, x), f => f(x));

const tAnd = tf => tg =>
  Task((res, rej) =>
    tf.runTask(f =>
      tg.runTask(g =>
        res([f, g]), rej),
        rej));

const tAll = tasks =>
  arrFold(acc => tf =>
    tMap(([xs, x]) =>
      (xs.push(x), xs)) // A
        (tAnd(acc) (tf)))
          (tOf([]))
            (tasks);

const main = tAll([
  delay(200, 'a'),
  delay(500, 'b'),
  delay(100, 'c')]);
  
const main2 = tAll([
  delay(100, 'd')]);

main.runTask(console.log, console.error); // ["d"]
main2.runTask(console.log, console.error); // ["d", "a", "b", "c"]