函数式JavaScript:闭包和递归。为什么这会失败?

3

希望实现的功能:

mult(3);
 //(x) => 3 * mult(x)
mult(3)(4);
 //(x) => 3 * (4 * mult(x))
mult(3)(4)();
//12

尝试:

function mult(x){
    if(x === undefined){
        return 1;
    }else{
        return (y => x * mult(y));
    }
}

结果:

mult(3)
//y => x * mult(y)
//looks pretty good

mult(3)()
//3
//exactly what I want so far. 

mult(3)(4)()
//Uncaught TypeError: mult(...)(...) is not a function

sure enough,

mult(3)(4)
//NaN

然而mult(3)看起来不错,而且typeof mult(3) === "function"

怎么回事? 我在JS中不能这么花哨吗?为什么不能呢?


1
想一想当你调用mult(3)(4)时会发生什么。你在调用(y => 3 * mult(y)),其中y4mult(4)返回什么?一个函数。3 * <一个函数>返回什么?NaN - Felix Kling
可能可以看一下这里的答案:JS Curry function with Recursion - Herohtar
请查看以下链接:https://dev59.com/-ZPfa4cB1Zd3GeqPDXWi - ltamajs
3个回答

2
如果仔细思考,应该永远不会将 mult 传递为 undefined,因为它代表了乘法的“当前”或左侧。接收 y 的内部函数应该处理提供结果的工作。

function mult(x){
    return y => {
        if (y === undefined) return x;
        else return mult(x * y);
    };
}

console.log(mult(3))
console.log(mult(3)())
console.log(mult(3)(4)())


太美了。绝对的美丽。我选择了 @melpomene 的答案,因为它清楚地显示了我的罪恶方式的错误,同时还展示了我所需的功能。 - Paul
@doodlemeister,由于对x的假设不当,无法处理空集mult() - Mulan

2

以下是另一种方法,它使用具有默认值的辅助参数:

  • 基本情况 - x 未定义,返回累加器 acc
  • 归纳情况 - x 已定义 - 返回一个新的 lambda 函数,要求下一个输入 y,并使用 yacc * x 进行递归

const mult = (x, acc = 1) =>
  x === undefined
    ? acc
    : y => mult (y, acc * x)

console.log (mult ())             // 1
console.log (mult (2) ())         // 2
console.log (mult (2) (3) ())     // 6
console.log (mult (2) (3) (4) ()) // 24

个人而言,我会使用一个已知(显式)的空值,而不是依赖隐式的undefined

const Empty =
  Symbol ()

const mult = (x = Empty, acc = 1) =>
  x === Empty
    ? acc
    : y => mult (y, acc * x)

console.log (mult ())             // 1
console.log (mult (2) ())         // 2
console.log (mult (2) (3) ())     // 6
console.log (mult (2) (3) (4) ()) // 24

实际上,如上所定义的mult只是一个玩具。更实用的实现应该使用可变接口而不是使用奇怪的柯里化和()来表示返回值。

// fixed arity
const mult = (x, y) =>
  x * y

// variadic interface
const multiply = (x = 1, ...xs) =>
  xs.reduce (mult, x)
  
console.log (multiply ())        // 1
console.log (multiply (2))       // 2
console.log (multiply (2, 3))    // 6
console.log (multiply (2, 3, 4)) // 24


1
我想我陷入了爱河。 - Paul
@Paul 我在递归方面的所有回答都是从函数式编程的角度来回答的。希望你会发现其中一些有用 ^_^ - Mulan

2

In

mult(3)(4)

mult(3)会产生y => 3 * mult(y)

因此

(y => 3 * mult(y))(4)

变成

3 * mult(4)

mult(4) yields y => 4 * mult(y).

3 * (y => 4 * mult(y))

这是无意义的,因为你试图用一个函数去乘以3。这就是为什么你在这里得到了NaN,而NaN本身不能再进一步应用。


可能的解决方案:

function mkmult(acc) {
    return x =>
        x === undefined
            ? acc
            : mkmult(acc * x);
}

const mult = mkmult(1);

console.log(mult(3)());
console.log(mult(3)(4)());
console.log(mult(3)(4)(5)());


等等,其实我的评论是正确的。你为什么要这样写 const mult = mkmult(1) 而不是直接定义为 mult 呢? - Patrick Roberts
@PatrickRoberts 因为否则 mult() 不会给你 1 - melpomene
那不是必需的要求吗? - Patrick Roberts
1
@PatrickRoberts 这在 OP 的代码中。而且这样做也是有道理的:我们可以将函数泛化为类似于左折叠的东西:function fold_left(acc, op) { return x => x === undefined ? acc : fold_left(op(acc, x), op); },在这种情况下,accx 甚至可以具有不同的类型。你所建议的类似于 foldl1,它强制 accx 具有相同的类型,并且不能用于空列表。 - melpomene
1
@melpomene 谢谢你向我展示了我的错误。你是我黑暗中的光芒;真理的灯塔。今晚,我会安心地休息。 - Paul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接