Вместо использования 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]
Почти каждая рекурсивная функция может быть определена с использованием складок (структурная рекурсия, или индукция).Например, даже функция Аккермана может быть определена с помощью сгибов:
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));
};
Я использовал итерацию по той же причине, по которой вы использовали итерацию для реализации loop
.Батуте.Реализуя разные батуты для каждого типа данных, которые вы собираетесь свернуть, вы можете писать функциональный код, не беспокоясь о переполнении стека.
Итог: Используйте сгибы вместо явной рекурсии,Они намного мощнее, чем вы думаете.