Как сложить структуры данных из не хвостовых рекурсивных алгоритмов? - PullRequest
3 голосов
/ 02 июня 2019

У меня есть функция подъема с переменным углом, которая допускает плоские монадические цепи без глубоко вложенной функции:

const varArgs = f => {
  const go = args =>
    Object.defineProperties(
      arg => go(args.concat(arg)), {
        "runVarArgs": {get: function() {return f(args)}, enumerable: true},
        [TYPE]: {value: "VarArgs", enumerable: true}
      });

  return go([]);
};

const varLiftM = (chain, of) => f => { // TODO: replace recursion with a fold
  const go = (ms, g, i) =>
    i === ms.length
      ? of(g)
      : chain(ms[i]) (x => go(ms, g(x), i + 1));

  return varArgs(ms => go(ms, f, 0));
};

Это работает, но я бы хотел абстрагироваться от рекурсии со сгибом.Нормальное сгибание, похоже, не работает, по крайней мере, вместе с типом Task,

const varLiftM = (chain, of) => f =>
  varArgs(ms => of(arrFold(g => mx => chain(mx) (g)) (f) (ms))); // A

, потому что алгебра в строке A будет возвращать Task для каждой итерации, а нечастично примененная функция.

Как заменить нехвостую рекурсию сгибом?

Вот рабочий пример текущей рекурсивной реализации:

const TYPE = Symbol.toStringTag;

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

  return cons(f);
};

// variadic argument transformer

const varArgs = f => {
  const go = args =>
    Object.defineProperties(
      arg => go(args.concat(arg)), {
        "runVarArgs": {get: function() {return f(args)}, enumerable: true},
        [TYPE]: {value: "VarArgs", enumerable: true}
      });

  return go([]);
};

// variadic monadic lifting function

const varLiftM = (chain, of) => f => { // TODO: replace recursion with a fold
  const go = (ms, g, i) =>
    i === ms.length
      ? of(g)
      : chain(ms[i]) (x => go(ms, g(x), i + 1));

  return varArgs(ms => go(ms, f, 0));
};

// asynchronous Task

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

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

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

const tChain = mx => fm =>
  Task((res, rej) => mx.runTask(x => fm(x).runTask(res, rej), rej));

// mock function

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

// test data

const tw = delay(100, 1),
  tx = delay(200, 2),
  ty = delay(300, 3),
  tz = delay(400, 4);

// specialization through partial application

const varAsyncSum =
  varLiftM(tChain, tOf) (w => x => y => z => w + x + y + z);

// MAIN

varAsyncSum(tw) (tx) (ty) (tz)
  .runVarArgs
  .runTask(console.log, console.error);

console.log("1 sec later...");

[РЕДАКТИРОВАТЬ] По желанию в комментариях моя сложенная реализация:

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

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

  return acc;
};

Ответы [ 2 ]

1 голос
/ 03 июня 2019

Вам не нужна полная мощность монад для этого конкретного случая использования. Аппликативные функторы - все, что вам нужно:

// type Cont r a = (a -> r) -> r

// type Async a = Cont (IO ()) a

// pure :: a -> Async a
const pure = a => k => k(a);

// ap :: Async (a -> b) -> Async a -> Async b
const ap = asyncF => asyncA => k => asyncF(f => asyncA(a => k(f(a))));

// delay :: (Number, a) -> Async a
const delay = (ms, a) => k => setTimeout(k, ms, a);

// async1, async2, async3, async4 :: Async Number
const async1 = delay(100, 1);
const async2 = delay(200, 2);
const async3 = delay(300, 3);
const async4 = delay(400, 4);

// sum :: Number -> Number -> Number -> Number -> Number
const sum = a => b => c => d => a + b + c + d;

// uncurry :: (a -> b -> c) -> (a, b) -> c
const uncurry = f => (a, b) => f(a)(b);

// result :: Async Number
const result = [async1, async2, async3, async4].reduce(uncurry(ap), pure(sum));

// main :: IO ()
result(console.log);
console.log("1 second later...");

Если вы хотите, вы можете определить аппликативную контекстную функцию (т.е. apply) следующим образом:

const apply = (asyncF, ...asyncArgs) => asyncArgs.reduce(uncurry(ap), asyncF);

const result = apply(pure(sum), async1, async2, async3, async4);

Если вы карри эту функцию, то вы можете создать функцию lift:

const apply = asyncF => (...asyncArgs) => asyncArgs.reduce(uncurry(ap), asyncF);

const lift = f => apply(pure(f));

const asyncSum = lift(sum);

const result = asyncSum(async1, async2, async3, async4);

Обратите внимание, что reduce является эквивалентом arrFold. Следовательно, lift эквивалентно varLiftM.

1 голос
/ 02 июня 2019

То, что of обзвонит arrFold, кажется немного неуместным.

Я не уверен, является ли ваш arrFold правым или левым сгибом, но, предполагая, что это правильный сгиб, вам нужно будет использовать стиль передачи продолжения с замыканиями, как вы это делали в своей рекурсивной реализации:

varArgs(ms => of(arrFold(g => mx => chain(mx) (g)) (f) (ms)))

становится

varArgs(ms => arrFold(go => mx => g => chain(mx) (x => go(g(x)))) (of) (ms) (f))

С левой складкой можно написать

varArgs(arrFold(mg => mx => chain(g => map(g) (mx)) (mg)) (of(f)))

но вы должны заметить, что это создает дерево вызовов, отличное от правильного сгиба:

of(f)
chain(of(f))(g0 => map(m0)(g0))
chain(chain(of(f))(g0 => map(m0)(g0)))(g1 => map(m1)(g1))
chain(chain(chain(of(f))(g0 => map(m0)(g0)))(g1 => map(m1)(g1)))(g2 => map(m2)(g2))

против (с уже примененными продолжениями)

of(f)
chain(m0)(x0 => of(f(x0)))
chain(m0)(x0 => chain(m1)(x1 => of(f(x0)(x1))))
chain(m0)(x0 => chain(m1)(x1 => chain(m2)(x2) => of(f(x0)(x1)(x2)))))

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

...