Javascript 无条件递归

Javascript recursion without conditional

我正在尝试 'implement' Church's encoding of lambda calculus 在 Javascript 中,但在我编写阶乘函数时开始遇到问题:

const factorial = n => (iszero(n))(ONE)(multiply(n)(factorial(predecessor(n))));

iszero(n) 充当条件,returns 一个函数,如果 n 为零则执行它的第一个参数,否则执行第二个参数。

对任何值调用此函数都会导致堆栈溢出。

我试图简化它以找出原因:

//const ifelse = condition => a => b => condition ? a : b;
//const factorial = n => ifelse(n == 0)(1)(n * factorial(n - 1));
const ifelse = function(condition, a, b) {
    if (condition) {
        return a;
    } else {
        return b;
    }
}
const factorial = function(number) {
    return ifelse(number == 0, 1, number * factorial(number - 1));
}

在这里调用阶乘也会导致溢出,尽管如果我们减少 factorial 中的 ifelse 函数,它会生成一个不会抛出的完美函数阶乘函数:

//const factorial = n => (n == 0) ? (1) : (n * factorial(n - 1));
const factorial = function(number) {
    if (number == 0) {
        return 1;
    } else {
        return number * factorial(number - 1);
    }
}

但是我试图在阶乘中只使用函数,所以三元条件运算符是不可接受的(参见注释箭头符号)。

为什么我的 factorial 函数在对任何值调用时会导致堆栈溢出,是否可以在仅使用函数(无条件)的情况下避免这种情况?

编辑:第一段代码的定义:

const TRUE = x => y => x;
const FALSE = x => y => y;

const ZERO = f => x => x;
const ONE = f => x => f(x);

const pair = a => b => f => f(a)(b);

const first = p => p(TRUE);
const second = p => p(FALSE);

const iszero = n => n(x => FALSE)(TRUE);

const successor = n => f => x => f(n(f)(x));
const multiply = n1 => n2 => f => x => n1(n2(f))(x);
const predecessor = n => second(n(p => pair(successor(first(p)))(first(p)))(pair(ZERO)(ZERO)));

您正在遇到堆栈溢出,因为 JavaScript 不是惰性求值语言。例如:

ifelse(false)(console.log("YES"))(console.log("NO"))
// => YES
//    NO

两个参数都被评估。防止这种情况发生的方法是将它们保留为函数,直到您需要它们。

ifelse(false)(() => console.log("YES"))(() => console.log("NO"))()
// => NO

因此在您的第二个 factorial 定义中,无论如何都会执行 factorial(n - 1)ifelse 只是为了让你 select 将两个值中的哪一个 return。显然,factorial(0) 然后会调用 factorial(-1),这将一直持续到 -infinity,受内存限制。

不对它们进行评估:

factorial = n => ifelse(n == 0)(() => 1)(() => n * factorial(n - 1)())
factorial(1)()
# => 1
factorial(5)()
// => 120

鉴于您没有定义,我无法告诉您第一组有什么问题,但原因可能是相同的。

编辑:看过定义后...让我先添加一个我自己的定义:

const display = n => n(x => x + 1)(0)

(n(x => x + 1)(0) 是一个将教堂数字转换为普通数字的便捷技巧,因此我们可以看到发生了什么。仅用于调试工具,因此它不应违反您正在做的事情的纯洁性。 )

我们的检查员在手... predecessor 不正确。参见:

display(successor(predecessor(ONE)))
// => TypeError: f(...) is not a function
//        at f (repl:1:33)
//        at x (repl:1:36)

试试这个(直接来自您的维基百科link):

const predecessor = n => f => x => n(g => h => h(g(f)))(u => x)(u => u)

有了这个,你会得到正确的结果:

display(successor(predecessor(ONE)))
// => 1

现在,转向阶乘。应用与上面相同的技巧(将 ifelse 分支包装到闭包中以防止它们过早评估):

const factorial = n => (iszero(n))(() => ONE)(() => multiply(n)(factorial(predecessor(n))()));

const FIVE = successor(successor(successor(successor(ONE))));
display(factorial(FIVE)())
// => 120

问题是函数参数在函数本身 运行 之前被完全求值。所以,例如,当

(n * factorial(n - 1))

是一个参数列表,那里的代码 必须 在继续调用由

返回的函数之前解析为一个值
n => ifelse(n == 0)(1)

它不会在评估之前首先检查您的 ifelse 条件 - 它无论如何都会尝试评估它。因此,导致堆栈溢出,因为它试图不断计算出 nelse 结果,然后是 n - 1,然后是 n - 2,... n - 10 , n - 11, 等等。

b 作为 函数 传递,这样您就可以调用它并将 else 计算为仅当条件最终为false:

const ifelse = condition => a => b => condition ? a : b();
const factorial = n => ifelse(n == 0)(1)(() => n * factorial(n - 1));
//                                       ^^^^^
console.log(factorial(5));