How do I curry this function correctly?

Viewed 140

I tried to curry a function add using curryDecorate

const curryDecorate = (fn, ...args) => {
  const curried = (...newArgs) => {
    args = args.concat(newArgs)
    if (args.length < fn.length) {
      return curried
    }
    return fn(...args)
  }
  return curried
}

const add = (a, b, c) => a + b + c
const sum = curryDecorate(add)

console.log(sum(1)(2)(3)) // 6
console.log(sum(1, 2)(3)) // 6
console.log(sum(1)(2, 3)) // 6

When sum(1, 2)(3) is run, I get

sum(...) is not a function

It works correctly when running the 3 lines at the bottom separately, but together it throws that error.

4 Answers

You're using the sum variable twice to curry, which only saves one group of arguments.

sum(1)(2)(3) // args: 1, 2, 3
sum(1, 2) // persists, so args: 1, 2, 3, 1, 2

This solves your problem:

const curryDecorate = (fn, ...args) => {
  let pass=[...args];
  const curried = (...newArgs) => {
    pass= pass.concat(newArgs)
    if (pass.length < fn.length) {
      return curried
    }
    const val = fn(...pass);
    pass=[...args]
    return val;
  }
  return curried
}

const add = (a, b, c) => a + b + c
const sum = curryDecorate(add)

console.log(sum(1)(2)(3)) // 6
console.log(sum(1, 2)(3)) // 6
console.log(sum(1)(2, 3)) // 6

const curryDecorate = (fn, ...args) => {
  const curried = (...newArgs) => {
    args = args.concat(newArgs)
    console.log('fn len', fn.length, args.length, args.length < fn.length)
    if (args.length < fn.length){
      return curried
    }
    const currArgs = [...args]
    args = []
    return fn(...currArgs)
  }
  return curried
}

const add = (a, b, c) => console.log(a + b + c)
const sum = curryDecorate(add)

sum(1)(2)(3)
sum(1)(2)(3)
sum(1)(2)(3)
sum(1)(2)(3)
sum(1)(2)(3)
  // 6

The problem is that you currently have one single instance of args that is shared between all versions of the decorated curried function. This means that once you acquire enough arguments once any further curried calls will just go directly to return fn(...args) and thus executing again will fail:

             console.log(sum (1) (2) (3))
//                           ^^^ ^^^ ^^^
//                            |   |   |
//args = [1] -----------------+   |   |
//args = [1, 2] ------------------+   |
//args = [1, 2, 3] -------------------+

             console.log(sum (1, 2) (3))
//                           ^^^^^^
//                             |
//args = [1, 2, 3, 1, 2] ------+ 

On the fourth call sum(1, 2) the args array contains [1, 2, 3, 1, 2] which is more than fn.length, therefore sum(1, 2) will execute the function and return 6. The next (3) will attempt to execute the number as a function and fail:

const curryDecorate = (fn, ...args) => {
  const curried = (...newArgs) => {
    args = args.concat(newArgs)
    if (args.length < fn.length) {
      return curried
    }
    return fn(...args)
  }
  return curried
}

const add = (a, b, c) => a + b + c
const sum = curryDecorate(add)

console.log(sum(1)(2)(3)) // 6
const result = sum(1, 2);
console.log(result)       // 6
console.log(result(3))    // error

You can take advantage of the fact that curryDecorate already takes args as a parameter, in order to avoid this error. If a curried call does not fulfil the argument requirements, you can call curryDecorate again with all arguments so far. That way you get separate function objects each with with a separate partially filled argument list:

const curryDecorate = (fn, ...args) => {
  const curried = (...newArgs) => {
    //collect all into newArgs
    newArgs = args.concat(newArgs)
    if (newArgs.length < fn.length) {
      //call curryDecorate with arguments so far
      return curryDecorate(fn, ...newArgs)
    }
    //execute with all collected args
    return fn(...newArgs)
  }
  return curried
}

const add = (a, b, c) => a + b + c
const sum = curryDecorate(add)

console.log(sum(1)(2)(3)) // 6
console.log(sum(1, 2)(3)) // 6
console.log(sum(1)(2, 3)) // 6

console.log("-----");

const sum1 = sum(1);

const sum14 = sum1(4);
const sum15 = sum1(5);

const sum144 = sum14(4);
const sum145 = sum14(5);
const sum155 = sum15(5);
const sum156 = sum15(6);

console.log(sum144);
console.log(sum145);
console.log(sum155);
console.log(sum156);
.as-console-wrapper { max-height: 100% !important; }

Taking another step forward, if curryDecorate is going to be called with the arguments anyway, the logic to execute the function or not can be moved there and things are simplified a bit:

const curryDecorate = (fn, ...args) => {
  if (args.length >= fn.length)
    return fn(...args);
    
  return (...newArgs) =>
    curryDecorate(fn, ...args, ...newArgs);
}

const curryDecorate = (fn, ...args) => {
  if (args.length >= fn.length)
    return fn(...args);
    
  return (...newArgs) =>
    curryDecorate(fn, ...args, ...newArgs);
}

const add = (a, b, c) => a + b + c
const sum = curryDecorate(add)

console.log(sum(1)(2)(3)) // 6
console.log(sum(1, 2)(3)) // 6
console.log(sum(1)(2, 3)) // 6

console.log("-----");

const sum1 = sum(1);

const sum14 = sum1(4);
const sum15 = sum1(5);

const sum144 = sum14(4);
const sum145 = sum14(5);
const sum155 = sum15(5);
const sum156 = sum15(6);

console.log(sum144);
console.log(sum145);
console.log(sum155);
console.log(sum156);
.as-console-wrapper { max-height: 100% !important; }

Related