'javascript flexible argument for curry function

I have a question regarding curry function.. I know that if I have this simple curry function:

const greeting = (greet) => {
  return (name) => {
    return `${greet} ${name}`;
  };
};

I can call greeting('Hello')('John') and it will return Hello John.

Is there a way to make it flexible say between 1 parameter and 2 parameters, ex: with the above greeting function, is there a way for me to call greeting('Hello') and greeting('Hello')('John') and it will return Hello and Hello John respectively?

I know that I can do it with greeting('Hello')() and greeting('Hello')('John') but I was just trying to avoid breaking changes because I already have a greeting method and want to extend it using curry function, so I want it to also accept greeting('Hello') without the extra () at the end...

thanks



Solution 1:[1]

I can think of only one option that works by coercing the curried function into a string. This won't change the return value but it will allow you to get the result you want depending on context.

const greeting = greet => Object.defineProperties(
  name => `${greet} ${name}`, // curried
  {
    toString: {
      value: () => greet,
    },
    valueOf: {
      value: () => greet
    }
  }
)

console.log(typeof greeting("Hello")) // function, not string
console.log(`${greeting("Hello")}`) // note the string context
console.log(`${greeting("Hello")("World")}`)

If you need the return value to actually toggle between a function and a string however, the answer is no.

In order for greeting("Hello")("John") to return a string, greeting("Hello") must return a function.

There is no way to tell within greeting() how the curried function is going to be called so you cannot detect whether or not to return a function or a string.

Think of it this way, greeting("Hello")("John") is just a short version of...

const fn = greeting("Hello")

// later or maybe never...

fn("John")

You simply don't know how, when or even if that curried function will be called.

Solution 2:[2]

Is there a way? Sure. But why? because won't that be "un-currying" it? And you will have to modify the function of-course.

You can always do something like this just get the output your asked for:

const greeting = (greet) => {
  const split = greet.split(" ");
  if(split.length > 1) 
       return `${split[0]} ${split[1]}`;
  else return (name) => {
    return `${greet} ${name}`;
  };
};

Solution 3:[3]

If you use a helper function for currying, you can get a similar behavior automatically. For example, take the implementation at javascript.info/currying-partials

function curry(func) {

    return function curried(...args) {
        if (args.length >= func.length) {
            return func.apply(this, args);
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            }
        } 
    };
}

You can define

const greeting = curry((greet, name) => `${greet} ${name}`)

and call

greeting("Hello", "John") 

or

greeting("Hello")("John")

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Shaunak
Solution 3 Steve Clanton