如何折叠非尾递归算法的数据结构?

时间:2019-06-02 17:22:46

标签: javascript recursion functional-programming fold recursion-schemes

我具有可变的提升功能,该功能可以实现平坦的单子链,而无需深层嵌套功能组成:

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;
};

[编辑]根据需要在我的折叠实现中添加注释:

{{1}}

2 个答案:

答案 0 :(得分:1)

围绕of的{​​{1}}通话似乎有点不合适。

我不确定您的arrFold是右折还是右折,但是假设它是右折,您将需要使用闭包的连续传递样式,就像在递归实现中一样:< / p>

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)))

vs(已应用延续)

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))

根据monad法则,它们的评估结果应相同,但实际上,一个法则可能比另一个法则更有效率。

答案 1 :(得分:1)

在此特定用例中,您不需要Monad的全部功能。您只需要适用函子:

// 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);

请注意,reducearrFold等效。因此,lift等效于varLiftM