Есть ли лучший способ для mimi c сделать запись в JS? - PullRequest
4 голосов
/ 14 февраля 2020

Монади c вычисления быстро становятся запутанными в 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.

Функции генератора кажутся подходящими, но они не работают с монадами, которые предоставляют приоритетный выбор. Поэтому я довольно долго искал альтернативу, а недавно придумал аппликатор типа monadi c, чтобы немного распутать вложенные вычисления:

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?

1 Ответ

3 голосов
/ 15 февраля 2020

Вы можете использовать библиотеку immutagen для написания кода monadi c с использованием генераторов.

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>

Как видите, это работает и с недетерминированными c монадами. Однако у него есть два недостатка:

  1. Это неэффективно, поскольку необходимо создавать и воспроизводить несколько генераторов, что приводит к квадратичному c росту временной сложности.
  2. Это работает только для чистого монады и чистые вычисления, потому что необходимо создать и воспроизвести несколько генераторов. Следовательно, побочные эффекты будут некорректно выполняться несколько раз.

Тем не менее, даже если вы не используете генераторы, написание кода monadi c в JavaScript не так уж и плохо.

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.

...