如何理解一个将选项对象作为参数而不是几个不同参数的函数?

时间:2017-05-21 17:42:25

标签: javascript functional-programming ecmascript-6 currying ramda.js

过去几天我一直在研究部分申请和讨论。

我想知道如何将这些概念用于只使用一个options对象作为参数的函数。

const myFunc = options => {
  const options.option1 = options.option1 || 'default value';
  const options.option2 = options.option2 || 'another default value';
  // ... etc, it takes about 5 more options, all of which have a
  // default fall-back value if not supplied

  return doSometing(options);
}

在这种情况下,我感觉不太好改变myFunc签名并将每个选项作为单独的参数传递,因为记住必须提供选项的顺序是一件痛苦的事。

我想坚持使用功能样式并避免使用new ...实例化对象以保持状态;我有预感,这可以通过部分应用来实现。它可以在测试或实例化时保持简单。

但是,如果没有单独的参数,我不知道如何正确地进行部分应用。

你将如何处理这个重构?

3 个答案:

答案 0 :(得分:2)

我建议相当于使用选项对象来调用函数与如何处理默认值相同。将此视为部分应用程序:

myFuncWithMyOptions(myFunc, myOptions) {
  return function(options) {
    return myFunc(Object.assign({}, myOptions, options));
  }
}

如果您希望myOptions中的选项不被options中的选项覆盖,只需将两个参数交换为Object.assign

答案 1 :(得分:2)

根据Dan D的回答和评论,这项技术可以让你重复部分应用这样的功能,直到提供了所有必需的字段:

const vals = (obj) => Object.keys(obj).map(key => obj[key]);

const addDefaults = (() => {
  const req = Symbol();
  const addDefaults = (defaults, fn) => (options) => {
    const params = Object.assign({}, defaults, options);
    return (vals(params).includes(req)) 
      ? addDefaults(params, fn)
      : fn(params);
  };
  addDefaults.REQUIRED = req;
  return addDefaults;
})();


const order = addDefaults({
  color: addDefaults.REQUIRED,
  size: addDefaults.REQUIRED,
  giftWrap: false,
  priority: false
}, (opts) => 
   `${opts.size}, ${opts.color}, ${opts.giftWrap ? '' : 'no'} wrap, priority: ${opts.priority}`
);

order({color: 'Red', size: 'Medium'}); // "Medium, Red, no wrap, priority: false"

const orderLarge = order({size: 'Large'}); // Options -> String
orderLarge({color: 'Blue'}); // "Large, Blue, no wrap, priority: false"

答案 2 :(得分:1)

我认为您的问题与部分申请无关。 myFunc究竟做了什么?

  • 设置了几个可选的默认值
  • 它调用另一个函数

这并不多。然而又出现了两个问题:

  • 功能组合被硬编码并隐藏在myFunc
  • 的正文中
  • 从功能签名中看不出哪些默认值被覆盖

简而言之,“正确”功能通过其签名显示其功能。那么让我们摆脱myFunc包装器:

const options = {
  foo: 1,
  bar: true,
  bat: "",
  baz: []
};

// function composition

const comp = (...fs) => x => fs.reduceRight((acc, f) => f(acc), x);

// applicator for partial application with right section

const _$ = (f, y) => x => f(x) (y); // right section

// `Object` assignment

const assign = o => p => Object.assign({}, o, p);

// specific function of your domain

const doSomething = o => (console.log(o), o);

// and run (from right-to-left)

comp(doSomething, _$(assign, {baz: [1, 2, 3], bat: "abc"})) (options);

现在,您无需查看函数体就可以准确地看到正在发生的事情。选项Object的属性顺序也无关紧要。

关于_$的评论。它有这个奇怪的名字,因为在这个特殊情况下,我更喜欢视觉名称而不是文字名称。鉴于函数sub = x => y => x - y_$(sub, 2)仅表示x => x - 2。这被称为部分应用的sub函数的右侧部分,因为“left”参数仍然缺失。