Paul
Paul

Reputation: 621

Functional JavaScript: Closure & Recursion. Why does this fail?

desired functionality:

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

attempt:

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

result:

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

Yet mult(3) looks good and typeof mult(3) === "function".

What gives? Can I not be this fancy in JS? Any why not?

Upvotes: 3

Views: 88

Answers (3)

Mulan
Mulan

Reputation: 135257

Here's another way that uses a secondary parameter with a default value

  • base case – x is undefined, return the accumulator acc
  • inductive case - x is not undefined – return a new lambda that asks for the next input y and recur using y and acc * 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

Personally, I would use a known (explicit) empty value rather than relying upon the implicit 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

Really though, mult as it is defined above is a toy. A more practical implementation would use variadic interface instead of the weirder currying with () used to signal a return value

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

Upvotes: 2

melpomene
melpomene

Reputation: 85777

In

mult(3)(4)

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

Thus

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

becomes

3 * mult(4)

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

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

is nonsense because you're trying to multiply 3 by a function. This is why you're getting NaN here, and NaN can't itself be further applied.


Possible solution:

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

Upvotes: 2

user9366559
user9366559

Reputation:

If you think about it, the mult should never be passed undefined since it represents the "current" or left side of the multiplication. The inner function receiving y is the one that should handle providing the result.

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

Upvotes: 2

Related Questions