3

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?

Derek 朕會功夫
  • 92,235
  • 44
  • 185
  • 247
Paul
  • 552
  • 5
  • 11
  • 1
    Think about what happens when you call `mult(3)(4)`. You are calling `(y => 3 * mult(y))`, where `y` is `4`. What does `mult(4)` return? A function. What does `3 * ` return? `NaN`. – Felix Kling Feb 19 '18 at 19:11
  • Probably take a look at the answers here: [JS Curry function with Recursion](https://stackoverflow.com/questions/48293642/js-curry-function-with-recursion) – Herohtar Feb 19 '18 at 19:14
  • check this: https://stackoverflow.com/questions/35039020/currying-a-function-that-takes-infinite-arguments – ltamajs Feb 19 '18 at 19:17

3 Answers3

2

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)());
melpomene
  • 84,125
  • 8
  • 85
  • 148
  • Wait actually, my comment was correct. Why are you doing `const mult = mkmult(1)` instead of just defining it as `mult`? – Patrick Roberts Feb 19 '18 at 19:29
  • @PatrickRoberts Because otherwise `mult()` doesn't give you `1`. – melpomene Feb 19 '18 at 19:30
  • That wasn't a requirement though? – Patrick Roberts Feb 19 '18 at 19:31
  • 1
    @PatrickRoberts It's in OP's code. And it makes sense anyway: We can generalize our function to something like a left fold: `function fold_left(acc, op) { return x => x === undefined ? acc : fold_left(op(acc, x), op); }` and in this case `acc` and `x` could even have different types. What you're suggesting is akin to `foldl1`, which 1) forces `acc` and `x` to have the same type and 2) doesn't work on empty lists. – melpomene Feb 19 '18 at 19:34
  • 1
    @melpomene Thank you for showing me the error of my ways. You are my light in the darkness; the lighthouse of truth. Tonight, I will rest easy. – Paul Feb 19 '18 at 19:52
2

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)())
  • It's beautiful. Utter beauty. I went with @melpomene 's answer because it clearly showed the error of my sinful ways in addition to how to provide the functionality I desire. – Paul Feb 19 '18 at 19:29
  • @doodlemeister, doesn’t work for empty set `mult ()` due to improper assumptions about `x` – Mulan Feb 19 '18 at 20:49
2

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
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    I think I'm in love. – Paul Feb 19 '18 at 19:46
  • @Paul all of [my answers on recursion](https://stackoverflow.com/search?tab=newest&q=user%3a633183%20%5brecursion%5d) are answered from a functional programming perspective. I hope you find some of them helpful ^_^ – Mulan Feb 19 '18 at 19:54