Как преобразовать мутацию внутри функциональной композиции в локальную, ненаблюдаемую?

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.

так сложно - какие приложения?

Kamil Kiełczewski 22.05.2019 15:34

Поможет ли обертка tOf([]) в функцию (() => tOf([])) и инициализация arrFoldacc = zero()? Или я упускаю суть?

user3297291 22.05.2019 15:44

@user3297291 user3297291 Нет, это все. Это немного странно, но это тоже Javascript, верно? Пожалуйста, опубликуйте это как ответ?

Iven Marquardt 22.05.2019 15:59
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Сравнение структур данных: Массивы и объекты в Javascript
Сравнение структур данных: Массивы и объекты в Javascript
Итак, вы изучили основы JavaScript и хотите перейти к изучению структур данных. Мотивация для изучения/понимания Структур данных может быть разной,...
Создание собственной системы электронной коммерции на базе Keystone.js - настройка среды и базовые модели
Создание собственной системы электронной коммерции на базе Keystone.js - настройка среды и базовые модели
Прошлая статья была первой из цикла статей о создании системы электронной коммерции с использованием Keystone.js, и она была посвящена главным образом...
Приложение для отслеживания бюджета на React js для начинающих
Приложение для отслеживания бюджета на React js для начинающих
Обучение на практике - это проверенная тема для достижения успеха в любой области. Если вы знаете контекст фразы "Практика делает человека...
Стоит ли использовать React в 2022 году?
Стоит ли использовать React в 2022 году?
В 2022 году мы все слышим о трендах фронтенда (React, Vue), но мы не знаем, почему мы должны использовать эти фреймворки, когда их использовать, а...
2
3
37
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы определили 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"]

Последний подход не является рекурсией, это просто эта-абстракция. Извините за путаницу.

Iven Marquardt 23.05.2019 09:26

Другие вопросы по теме