Skip to content

Instantly share code, notes, and snippets.

@ktilcu
Last active August 15, 2017 20:38
Show Gist options
  • Save ktilcu/a75559a3fed98a96ab39961c08f3a842 to your computer and use it in GitHub Desktop.
Save ktilcu/a75559a3fed98a96ab39961c08f3a842 to your computer and use it in GitHub Desktop.
kyle-Opinion: Avoid partials!

During a recent discussion Nathan made 2 excellent points about partials:

  1. partials can be unneeded.
  2. partials can be opaque.

I would like to support those and add a bit of advice:

  1. Avoid partials!

Un-needed _.partial

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.

Opaque _.partial

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.

Advice: Avoid _.partial

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?)

Examples of acceptable _.partial use

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment