0

That sounds kind of complex. The idea here is to write the method "defaultMethod". It receives a function and an object. If the function is a simple add:

function add(a,b) { return a+b;};

When calling

var add_ = defaultMethod(add,{b:9});
add_(10)

The behavior is:

  • default value for a: undefined
  • default value for b: 9
  • received value for a: 10
  • received value for b: undefined

and the return must be 19.

The catch is that the method can be called more than once:

var add_ = defaultMethod(add,{b:9}); // set 'b' default value as 9
    add_ = defaultMethod(add_,{b:3, a:2}); // now, set 'b' default value as 3 and 'a' as 2
    let res = add_(10) //sent 'a' value as 10
    expect(res).toBe(13); //10 (received) + 3 (default) 

I wrote it like this:

function defaultMethod(func, params) {
    var funcStr = func.toString();
    let requiredArgs = funcStr
      .slice(funcStr.indexOf('(') + 1, funcStr.indexOf(')')) //get the between parenthesis part
      .match(/([^\s,]+)/g) || []; //resulting in ['a', 'b']

    console.log(requiredArgs)
  
    return function (...args) {
      let calledArgs = args;
  
      if (calledArgs.length < requiredArgs.length) {
        for (let i = calledArgs.length; i < requiredArgs.length; i++) {
          if (calledArgs[i] === undefined) {
            calledArgs[i] = params[requiredArgs[i]];
          }
        }
      }
  
      return func(...calledArgs);
    };
  }
  

It works well for one calling, for example, all of these unit tests passes:

var add_ = defaultMethod(add,{b:9});

it('should return 19', () => {
    expect(add_(10)).toBe(19);
})

it('should return 17', () => {
    expect(add_(10,7)).toBe(17);
})

it('should return nan', () => {
    expect(add_()).toBe(NaN);
})

Although, when we call the defaultMethod one more time, now passing the add_ function, it starts to break. The console.log(requiredArgs) starts to log [...args] instead of ['a', 'b'].

The unit tests are the following:

var add_ = defaultMethod(add,{b:9}); // set b default value as 9
    add_ = defaultMethod(add_,{b:3, a:2}); // now, set b default value as 3 and a as 2

it('should return 13', () => {
    expect(add_(10)).toBe(13); //10 (received) + 3 (default) 
})//this one breaks returning 19

it('should return 5', () => {
    expect(add_()).toBe(5);
})//this one breaks returning NaN

it('should return nan', () => {
    add_ = defaultMethod(add_,{c:3}); // this doesn't do anything because c isn't required
    expect(add_(10)).toBe(NaN);
})//this one breaks returning 19

And I can't figure a way to make it work for more than one calling. Apparently, GPT-4 neither. Any ideas?

edit: I should note that the requirements are:

  • to not use global scope (outside of the function)
  • we must retrieve the arguments through func.toString()
Bipe
  • 27
  • 5

2 Answers2

1

Do not rely on func.toString(). As you've demonstrated yourself, there are many ways in which this can and does break; it is not a generally solvable problem. Parameter names are an implementation detail and should be considered unreliable.

Instead, have your function accept an object with named properties, you can easily merge those:

function add({a, b}) { return a+b; }
function defaultArgs(fn, def) { return arg => fn({...def, ...arg}); }

With this, your test cases work:

const add9 = defaultArgs(add, {b: 9});
console.log(add9({a: 10})); // 19
console.log(add9({a: 9, b: 6})); // 15
const add32 = defaultArgs(add9, {a: 3, b: 2});
console.log(add32()); // 5
console.log(add32({b: 10})); // 13
console.log(add32({a: 2})); // 4
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • It is an elegant solution, but for this exercise it was required that I don't send the arguments as objects like you did. I understand that in the real world it is the best practice, but in this code challenge it was required to not use it. Also, the "hint" of the challenge was to use the `.toString()` method to retrieve the parameters – Bipe Jun 18 '23 at 20:30
  • In that case, you should validate the parameter declaration of the function to have the expected syntax, and not just split on commas until the first closing parenthesis, then fail with a clear error message if the parameters have unhandled syntax. – Bergi Jun 18 '23 at 21:00
  • 1
    To fix the issue with the result of `defaultMethod` again being usable in `defaultMethod`, you can either [create a function with a particular parameter list](https://stackoverflow.com/a/24032179/1048572), or you can overwrite the `.toString()` method of the returned function. – Bergi Jun 18 '23 at 21:01
  • Indeed a great solution! Thank you for the contribution, @Bergi – Bipe Jun 18 '23 at 21:31
1

A solution could be to maintain a registry of functions that are returned by defaultMethod. In your example it would register the function add_ and the parameter names. Then if defaultMethod is called with that function as argument, you can find it in the registry and learn about the parameter names.

So the code would change like this:

function add(a,b) { return a + b; }

const defaultMethod = (function () { // Create a closure for `registry`
    const registry = new WeakMap;
    
    return function (func, params) {
        let requiredArgs = registry.get(func);
        if (!requiredArgs) {
            const funcStr = func.toString();
            requiredArgs = funcStr
              .slice(funcStr.indexOf('(') + 1, funcStr.indexOf(')'))
              .match(/([^\s,]+)/g) || [];
        }

        console.log("parameters are:", ...requiredArgs);
      
        const decoratedFunc = function (...args) {
            let calledArgs = args;
      
            for (let i = calledArgs.length; i < requiredArgs.length; i++) {
                if (calledArgs[i] === undefined) {
                    calledArgs[i] = params[requiredArgs[i]];
                }
            }
      
            return func(...calledArgs);
        };
        // Register the function we are about to return
        registry.set(decoratedFunc, requiredArgs);
        return decoratedFunc;
    };
})();
  
console.log("set defaults for add_ to {b:9}");
let add_ = defaultMethod(add,{b:9});
console.log("call add_(10): expect 19");
console.log(add_(10));
console.log("set defaults for add_ to {b:3, a:2}");
add_ = defaultMethod(add_,{b:3, a:2});
console.log("call add_(10): expect 13");
console.log(add_(10));
console.log("call add_(): expect 5");
console.log(add_());
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Indeed it solves the problems. I didn't know about the registry possibility because I thought it would not be possible to persist the data in a function if it wasn't a singleton that kept being called. Good to know about this. Thank you! – Bipe Jun 18 '23 at 21:40