During a recent discussion Nathan made 2 excellent points about partials:
- partials can be unneeded.
- partials can be opaque.
I would like to support those and add a bit of advice:
- Avoid partials!
For the first point this example was given:
function setJobStatus(success) {
// do stuff
}
// Partial is bad here because we're creating a whole new function that the programmer
// has to keep in mind, just to set one argument to false.
const onFail = _.partial(setJobStatus, false);
onFail();
// compared to just calling setJobStatus directly.
setJobStatus(false);
I agree. Using _.partial
here is unecessary and just adds more surface area for ambiguity. This usage hints at the power of _.partial
and its big brother _.curry
but ultimately is not great.
Another comment that came up for this usage was:
Is there any difference from just actually declaring a new function that calls the other function in it's implementation?
e.g.:
const renderOmg = _.partial(render, 'omg');
// is the same as
const renderOmg = () => render('omg');
// is the same as
const renderOmg = render.bind(null, 'omg');
The answer in this case is, there is no difference between them. Which is why I aggree with _.partial
being unneeded here.
The next argument is that using _.partial
obscures the function being executed more than a lambda. Again, I agree with this particular sentiment. It's hard to be more transparent that a lambda.
previewCampaign.load
.then(_.partial(assignCampaignData, vm))
.then(_.partial(safeApply, $scope));
// compared to
previewCampaign.load
.then((campaign) => assignCampaignData(vm, campaign))
.then(() => safeApply($scope));
This example helps to highlight that _.partial
can make it difficult to tell what parameters are actually being passed to the called function. In the first block it's difficult to see what is being passed into and subsequently used in the assignCampaignData
function. Does it use anything passed in by the .then
function? or is it another unneeded use of _.partial
? I think the first instance is an ok use of partial ( it's more like a bind
or a curry
) but I see the confusion. I also think that passing a declared function into another function is not the opaque part of this. Instead I think the use of partials increases the brain space necessary to follow the logic. Here's an example:
// function this example is based on
function readFile (path, callback) {
// read file
callabck(data);
}
// Usage 1: lambda, no partials
readFile('omg', data => // process file data)
// Usage 2: declared function, no partials
const processFileData = data => // do stuff;
readfile('omg', processFileData);
In the first we use an anonymous inline function to process data. It is very clear whats happening in the function and what parameters are being used. However, the processFileData
logic can't be reused at all because it's anonymous and inline. So, why not put the processFileData
in it's own function?
In the second example, we declare a function. It's less clear in the usage if the function uses the parameters passed to it but, becuase the function is declared we can reuse it. This is not considered opaque, It is a best practice.
The hope in this example is to show that passing functions as parameters is a best practice. Using _.partial
has an opportunity to confuse people.
I know this sounds like heresy for me but I think it holds true. I'm not saying don't use _.partial
but rather just to know exactly why when you do. It can be confusing, unneeded and opaque. While I advise to avoid _.partial
, I encourage exporting curried functions instead. (topic for another post maybe?)
The most useful case for _.partial
, in my mind, is when you are going to get arguments in a different order than was expected when the original function was written. This is often a problem when mixing legacy code with newer, more functional code.
e.g., Imagine this example posted in chat. A function meant to clean and extend widgets:
const cleanAndExtendWidget = (widget, json, usedAsstes, model) => /* blah */;
Now, suppose we have a list of widgets from the same creative that we would like to clean and extend. if we had a where we only haad to pass in the widget then we could very easily loop through the widgets and apply this functio to each one. Unfortunately, we have a function that takes 4 params, three of which don't ever change (in our case); This function is also legacy, so we can't just swap the parameter order around or change the function. This is an example of a time when _.partial
could be acceptable.
// setup
var widgets = [...]
var json = /* thing */;
var assets = [/*stuff*/];
var model = /* other thing */;
//Implementation 1: imperative with for loop, Please dont
var squeakyCleanWidgets = [];
for (var i=0, len=widgets.length; i<len; i++) {
var cleaned = cleanAndExtendWidget(widgets[i], json, assets, model);
squeakyCleanWidgets.push(cleaned);
}
// Implementation 2: declared function with for loop, not ideal
const cleanWidgets = (widgets, json, assets, model) => {
var squeakyCleanWidgets = [];
for (var i=0, len=widgets.length; i<len; i++) {
var cleaned = cleanAndExtendWidget(widgets[i], json, assets, model);
squeakyCleanWidgets.push(cleaned);
}
return squeakyCleanWidgets;
}
var cleaned = cleanWidgets(widget, json, assets, model);
// Implementation 3: _.partial and Array.prototype.map, Acceptable
var _cleanAndExtendWidget = _.partial(cleanAndExtendWidget, _, json, usedAssets, model);
var squeakyCleanWidgets = widgets.map(_cleanAndExtendWidget);
// Implementation 4: Wrapper function, with .map! Also Acceptable but with the added bonus of no partial to think about.
const cleanWidgets = (json, assets, model, widgets) => widgets.map(w=> cleanAndExtendWidget(w, json, assets, model));
const sqeuakyCleanWidgets = cleanWidgets(json, assets, model, widgets);
So while I can see the use for _.partial
in certain cases I think there is usually an easy way around it.