Как преобразовать мутацию внутри композиции функции в локальную ненаблюдаемую? - PullRequest
2 голосов
/ 22 мая 2019

Task - это монадический тип, представляющий последовательно выполняемые асинхронные вычисления. Как и в случае 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 Ответ

2 голосов
/ 22 мая 2019

Вы определили 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 использовать аргумент "lazy" 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"]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...