2

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?

springloaded
  • 1,079
  • 2
  • 13
  • 23
  • If you only have one argument, then there's nothing to curry... – elclanrs May 21 '17 at 17:47
  • See [Can we set persistent default parameters which remain set until explicitly changed?](http://stackoverflow.com/questions/43466657/can-we-set-persistent-default-parameters-which-remain-set-until-explicitly-chang) – guest271314 May 21 '17 at 18:01
  • @elclanrs: Absolutely. This is some other, perhaps interesting, form of partial application. – Scott Sauyet May 21 '17 at 19:26
  • It's a wrapper around request which itself takes a bazillion options via its options object. – springloaded May 21 '17 at 22:38

3 Answers3

3

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

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Dan D.
  • 73,243
  • 15
  • 104
  • 123
  • And if there are some options properties which have no default and are therefore required, you could extend this by either supplying a list of the names of the required properties, or by including them in `myOptions` with a value that signals that a call made without them is incomplete (`undefined` is possible if that can never be the value, or some `Symbol` attached to `myFuncWithMyOptions`.) If an incomplete object is sent, you can return a newly wrapped version of the original function with an extended set of defaults, and a reduced set of required fields. – Scott Sauyet May 21 '17 at 18:17
  • @ScottSauyet Yes, that might make debugging easier as the call would fail earlier. But it wouldn't change its effect. – Dan D. May 21 '17 at 18:21
  • Well, I was thinking that this would then work like other forms of partial application. If you don't supply all the required parameters, you simply get back a new function that will merge its options with the previous ones (and the defaults) and call the original function with them (assuming you've now supplied all the required ones.) – Scott Sauyet May 21 '17 at 18:50
  • @DanD. I think Scott is referring to currying here. To curry a usual function, you need to know its arity, to "curry" a option-object function you need to know which properties it expects. – Bergi May 21 '17 at 20:51
  • If you are permitted to supply values for the same option more than once or more than one option at a time then there is no fixed arity even if there is a fixed number of properties. Then it isn't reasonable to curry but rather one has to keep explicitly partially applying. – Dan D. May 21 '17 at 21:20
  • I'll make another answer out of my idea. It's probably easier to see that way. – Scott Sauyet May 21 '17 at 22:36
2

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"
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
1

I don't think your problem is connected with partial application. What exactly does myFunc do actually?

  • it sets a couple of optional default values
  • it invokes another function

This is not much. And yet two problems arise:

  • the function composition is hard coded and hidden in the body of myFunc
  • it doesn't get apparent from the function signature which default values are overwritten

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.

  • Dude this answer was a hammerblow holy cow. I've done a lot of reading on fp (and just recently tried to implement strictly) but I've mostly just come across theory and not much implementation. From your answer alone I learned: not everything needs to be curried; sometimes partial application makes more sense, an applicator is a function that takes a function and applies it to a value, and how to work with defaults. Incredible answer! I can't believe it doesn't have more upvotes!!! – 55 Cancri Nov 09 '19 at 22:09