Function that receives a function and a "default parameters" key-value object and returns another function with default arguments set

huangapple go评论68阅读模式
英文:

Function that receives a function and a "default parameters" key-value object and returns another function with default arguments set

问题

以下是翻译好的内容:

这听起来有点复杂。这里的想法是编写方法"defaultMethod"。它接收一个函数和一个对象。如果函数是一个简单的加法:

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

当调用:

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

行为是:

  • 参数a的默认值:undefined
  • 参数b的默认值:9
  • 参数a的接收值:10
  • 参数b的接收值:undefined

返回值必须是19。

问题在于该方法可以被多次调用:

var add_ = defaultMethod(add, { b: 9 }); // 设置'b'的默认值为9
    add_ = defaultMethod(add_, { b: 3, a: 2 }); // 现在,设置'b'的默认值为3,'a'的默认值为2
    let res = add_(10) // 设置'a'的值为10
    expect(res).toBe(13); // 10 (接收) + 3 (默认)

我像这样编写了它:

function defaultMethod(func, params) {
    var funcStr = func.toString();
    let requiredArgs = funcStr
      .slice(funcStr.indexOf('(') + 1, funcStr.indexOf(')')) // 获取括号之间的部分
      .match(/([^\s,]+)/g) || []; // 结果为['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);
    };
}

它对于一次调用效果很好,例如,所有这些单元测试都通过:

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

it('应该返回19', () => {
    expect(add_(10)).toBe(19);
})

it('应该返回17', () => {
    expect(add_(10, 7)).toBe(17);
})

it('应该返回NaN', () => {
    expect(add_()).toBe(NaN);
})

尽管如此,当我们再次调用defaultMethod时,现在传递了add_函数,它开始出现问题。console.log(requiredArgs)开始记录[...args]而不是['a', 'b']

单元测试如下:

var add_ = defaultMethod(add, { b: 9 }); // 设置b的默认值为9
    add_ = defaultMethod(add_, { b: 3, a: 2 }); // 现在,设置b的默认值为3,a的默认值为2

it('应该返回13', () => {
    expect(add_(10)).toBe(13); // 10 (接收) + 3 (默认)
})//这个测试失败返回19

it('应该返回5', () => {
    expect(add_()).toBe(5);
})//这个测试失败返回NaN

it('应该返回NaN', () => {
    add_ = defaultMethod(add_, { c: 3 }); // 这不起作用,因为c不是必需的
    expect(add_(10)).toBe(NaN);
})//这个测试失败返回19

我无法想出一种方法使其适用于多次调用。显然,GPT-4也不行。有什么想法吗?

编辑:我应该注意到要求是:

  • 不要使用全局作用域(函数外部)
  • 我们必须通过func.toString()来检索参数。
英文:

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 &#39;b&#39; default value as 9
    add_ = defaultMethod(add_,{b:3, a:2}); // now, set &#39;b&#39; default value as 3 and &#39;a&#39; as 2
    let res = add_(10) //sent &#39;a&#39; 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(&#39;(&#39;) + 1, funcStr.indexOf(&#39;)&#39;)) //get the between parenthesis part
	  .match(/([^\s,]+)/g) || []; //resulting in [&#39;a&#39;, &#39;b&#39;]

	console.log(requiredArgs)
  
	return function (...args) {
	  let calledArgs = args;
  
	  if (calledArgs.length &lt; requiredArgs.length) {
		for (let i = calledArgs.length; i &lt; 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(&#39;should return 19&#39;, () =&gt; {
    expect(add_(10)).toBe(19);
})

it(&#39;should return 17&#39;, () =&gt; {
    expect(add_(10,7)).toBe(17);
})

it(&#39;should return nan&#39;, () =&gt; {
    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 [&#39;a&#39;, &#39;b&#39;].

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(&#39;should return 13&#39;, () =&gt; {
    expect(add_(10)).toBe(13); //10 (received) + 3 (default) 
})//this one breaks returning 19

it(&#39;should return 5&#39;, () =&gt; {
    expect(add_()).toBe(5);
})//this one breaks returning NaN

it(&#39;should return nan&#39;, () =&gt; {
    add_ = defaultMethod(add_,{c:3}); // this doesn&#39;t do anything because c isn&#39;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()

答案1

得分: 1

不要依赖于 func.toString()。正如你所展示的,有许多可能导致错误的情况;这并不是一个普遍可解决的问题。参数名称是一个实现细节,应被视为不可靠的。

相反,让你的函数接受一个带有命名属性的对象,你可以轻松地合并它们:

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

使用这种方式,你的测试案例会生效:

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
英文:

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 =&gt; 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

答案2

得分: 1

一个解决方案可能是维护一个由defaultMethod返回的函数的注册表。在你的示例中,它将注册函数add_和参数名称。然后,如果defaultMethod函数作为参数调用,你可以在注册表中找到它并了解参数名称。

所以代码会像这样更改:

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

const defaultMethod = (function () { // 为`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("参数是:", ...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);
        };
        // 注册我们即将返回的函数
        registry.set(decoratedFunc, requiredArgs);
        return decoratedFunc;
    };
})();

console.log("设置add_的默认值为{b:9}");
let add_ = defaultMethod(add, { b: 9 });
console.log("调用add_(10):期望得到19");
console.log(add_(10));
console.log("设置add_的默认值为{b:3, a:2}");
add_ = defaultMethod(add_, { b: 3, a: 2 });
console.log("调用add_(10):期望得到13");
console.log(add_(10));
console.log("调用add_():期望得到5");
console.log(add_());

请注意,以上是对给定代码的翻译。如果你有任何其他问题或需要进一步解释,请随时提出。

英文:

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:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

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(&#39;(&#39;) + 1, funcStr.indexOf(&#39;)&#39;))
.match(/([^\s,]+)/g) || [];
}
console.log(&quot;parameters are:&quot;, ...requiredArgs);
const decoratedFunc = function (...args) {
let calledArgs = args;
for (let i = calledArgs.length; i &lt; 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(&quot;set defaults for add_ to {b:9}&quot;);
let add_ = defaultMethod(add,{b:9});
console.log(&quot;call add_(10): expect 19&quot;);
console.log(add_(10));
console.log(&quot;set defaults for add_ to {b:3, a:2}&quot;);
add_ = defaultMethod(add_,{b:3, a:2});
console.log(&quot;call add_(10): expect 13&quot;);
console.log(add_(10));
console.log(&quot;call add_(): expect 5&quot;);
console.log(add_());

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年6月19日 03:55:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76502316.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定