gpt4 book ai didi

javascript - 我们可以实现尾递归模 cons 等吗?通过蹦床?

转载 作者:行者123 更新时间:2023-12-02 23:43:35 28 4
gpt4 key购买 nike

您可以将蹦床视为程序中具体化的编译器优化。那么是什么阻止我们以完全相同的方式采用更通用的优化技术。

这是尾递归取模的草图缺点:

const loop = f => {
let step = f();

while (step && step[step.length - 1] && step[step.length - 1].type === recur) {
let step_ = step.pop();
step.push(...f(...step_.args));
}

return step;
};

const recur = (...args) =>
({type: recur, args});

const push = (xs, x) => (xs.push(x), xs);

const map = f => xs =>
loop((i = 0) =>
i === xs.length
? []
: push([f(xs[i])], recur(i + 1)));

const xs =
map(x => x * 2) (Array(1e6).fill(0).map((x, i) => i))
.slice(0,5);

console.log(xs); // [0, 2, 4, 6, 8]

这种优化取决于表达式的关联性属性。乘法也是结合律,因此存在尾递归模乘法。但是,我必须作弊才能在 Javascript 中实现它:

const loop = f => {
let step = f();
const acc = [];

while (step && step[1] && step[1].type === recur) {
acc.push(step[0]);
step = f(...step[1].args);
}

return acc.reduce((acc, f) => f(acc), step);
};

const recur = (...args) =>
({type: recur, args});

const mul = x => step => [y => x * y, step];

const pow = (x_, n_) =>
loop((x = x_, n = n_) =>
n === 0 ? 1
: n === 1 ? x
: mul(x) (recur(x, n - 1)));

console.log(
pow(2, 1e6)); // Infinity, no stack overflow

正如你所看到的,我不能使用常规的 mul,这并不是特别令人满意。这与 Javascript 作为一种严格的语言有关吗?有没有更好的方法可以在 JS 中实现尾递归模乘,而不必引入笨拙的二元运算符?

最佳答案

不要使用loop/recur(我认为这是一种丑陋且不必要的黑客),请考​​虑使用折叠:

const recNat = (zero, succ) => n => {
let result = zero;

while (n > 0) {
result = succ(result);
n = n - 1;
}

return result;
};

const mul = x => y => x * y;

const pow = x => recNat(1, mul(x));

console.log([0,1,2,3,4,5,6,1e6].map(pow(2))); // [1,2,4,8,16,32,64,Infinity]

几乎每个递归函数都可以使用折叠来定义(又名结构递归,又名归纳)。例如,即使是 Ackermann function可以使用折叠来定义:

const recNat = (zero, succ) => n => {
let result = zero;

while (n > 0) {
result = succ(result);
n = n - 1;
}

return result;
};

const add = x => y => x + y;

const ack = recNat(add(1),
ackPredM => recNat(ackPredM(1),
ackMPredN => ackPredM(ackMPredN)));

console.time("ack(4)(1)");
console.log(ack(4)(1)); // 65533
console.timeEnd("ack(4)(1)");

上面的代码片段在我的笔记本电脑上计算答案大约需要 18 秒。

现在,您可能会问为什么我使用迭代而不是自然递归来实现 recNat:

const recNat = (zero, succ) => function recNatZS(n) {
return n <= 0 ? zero : succ(recNatZS(n - 1));
};

我使用迭代的原因与您使用迭代来实现循环的原因相同。蹦床。通过为要折叠的每种数据类型实现不同的蹦床,您可以编写功能代码,而不必担心堆栈溢出。

底线:使用折叠而不是显式递归。他们比你想象的要强大得多。

关于javascript - 我们可以实现尾递归模 cons 等吗?通过蹦床?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55957286/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com