Reputation: 1079
I have been looking into partial application and currying over the last few days.
I'm wondering how could I use these concepts with a function that only takes one options
object as argument.
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);
}
In that case, I don't feel good changing the myFunc
signature and pass every option as a separate argument because it's a pain to remember the order in which the options must be supplied.
I'd like to stick with a functional style and avoid instantiating objects with new ...
just to keep state; I have a hunch this can be achieved with partial application. It keeps things simpler when it's time for testing, or to instantiate.
But then, I don't know how to do partial application properly without separate arguments.
How would you handle this refactor?
Upvotes: 2
Views: 989
Reputation:
I don't think your problem is connected with partial application. What exactly does myFunc
do actually?
This is not much. And yet two problems arise:
myFunc
Simply put, a "proper" function reveals its functionality by its signature. So let's get rid of the myFunc
wrapper:
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);
Now you can exactly see what is going on without having to look into the function bodies. The property order of the options Object
doesn't matter either.
A remark on _$
. It has this odd name because I prefer a visual name over a textual one in this particular case. Given the function sub = x => y => x - y
, _$(sub, 2)
simply means x => x - 2
. This is called the right section of the partially applied sub
function, because the "left" argument is still missing.
Upvotes: 1
Reputation: 50807
Following on Dan D's answer and the comments, this technique would let you partially apply such a function repeatedly until all the required fields are supplied:
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"
Upvotes: 2
Reputation: 74655
I would suggest that the equivalent of currying a function taking an option object would be the same as how to handle defaults. Consider this as a partial applier:
myFuncWithMyOptions(myFunc, myOptions) {
return function(options) {
return myFunc(Object.assign({}, myOptions, options));
}
}
If you want the options in myOptions
not be be overridden by those in options
simply swap the two arguments to Object.assign
Upvotes: 3