wdonahoe
wdonahoe

Reputation: 1073

Lodash using rearg in chain

I've tried using rearg with and without chaining, and I only get the error when using chaining. I've read up on the docs and it looks like rearg ought to play nicely with chaining as it returns a chainable wrapper method. However, I get the following error:

Error: _.chain(...).keys(...).myFunc is not a function

with this code:

var myList = ['a','b','c','d']
var myJSON = {
  'a':1,
  'b':2
};

var myFunc = _.rearg(_.difference, [1, 0]); //switching order of arguments

var hasAllKeys = _.chain(myJSON)
                  .keys()
                  .myFunc(myList)
                  .value();

Of course this code works fine (though the output is not what I need):

var wrong = _.chain(myJSON)
             .keys()
             .difference(myList)
             .values();

Upvotes: 2

Views: 599

Answers (2)

Mariano Desanze
Mariano Desanze

Reputation: 8163

This answer expands on PhiLho's excellent answer. Hopefully this will help future readers to understand a little more about the problem and solutions. In the following code snippet, you can see the same results as in PhiLho's code snippet and also other 2 tests:

  1. Chaining _.difference instead of _.reverseDiff: Which returns results when the object has some keys not found in keyList (instead of being the keyList the one that has keys not found in the object).
  2. The straightforward alternative (_.difference(myList, _.keys(myJSON))): which returns the expected output with less code (the intent of this question probably was not about this solution, but by showing this alternative you can probably understand the problem more easily).

// Generic code to display resuts
var results = document.getElementById('pls-results');
function showHTML(html) {
  results.insertAdjacentHTML('beforeend', html);
}

// Set up test data
var keyList = [ 'a', 'b', 'c', 'd' ];
var incompleteJson = { "a": 1, "b": 2 };
var fullJson = { "a": 1, "b": 2, "c": true, "d": "yes" };
var extraPropertyJson = { "a": 1, "b": 2, "c": true, "d": "yes", 'z': 26 };

// Set up test helper methods
var renderLine = function(label, obj) {
  return "<tr><td>" + label + "</td><td>" + JSON.stringify(obj) + "</td></tr>";
};
var test = function(funcName, funcToTest) {
  var html = "<h3>" + funcName + "</h3><table>";
  html += renderLine("Incomplete Json", funcToTest(incompleteJson));
  html += renderLine("Full Json", funcToTest(fullJson));
  html += renderLine("Extra Property Json", funcToTest(extraPropertyJson));
  html += "</table>";
  showHTML(html);
};

// The real code
var local_ = _.runInContext();
local_.mixin({
  'reverseDiff': _.rearg(_.difference, [1, 0])  // switching order of arguments
});

// Tests execution
test("_.difference",                        function(json) { return      _.chain(json).keys(). difference(keyList).value(); });
test("local_.reverseDiff",                  function(json) { return local_.chain(json).keys().reverseDiff(keyList).value(); });
test("_.difference(keyList, _.keys(json))", function(json) { return _.difference(keyList, _.keys(json)); });
<div id="pls-results"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>

Upvotes: 1

PhiLho
PhiLho

Reputation: 41132

I know the question is old, but it is one of the rare SO questions about chaining in Lodash remaining unanswered, and trying to answer it allows me to dig deeper in the documentation and in understanding this library... And it is a good opportunity to try the "Run JS code" feature of Stack Overflow...

I finally understood that it cannot work as you did.

First, is rearg really chainable? The answer is: yes. I tried with this code:

var last = _(twoParams).ary(1).rearg([1, 0]).value();

and indeed, when I call last with two parameters, it calls twoParams with only the last one.

Of course, rearg can be only chained with functions providing a function as output, and expecting a function as input, ie. only in a chain of functions processing functions.

Now, your code doesn't work because if rearg is chainable, its output isn't! Its output is a plain old JavaScript function, and these are not chainable. The good news is that Lodash, in its great wisdom, provides a way to make a function to be chainable. You have to use mixin which, by default, adds the provided function to Lodash and makes it chainable. See, for example, Create chain in lodash with custom functions which explains how to do it. Unlike these examples, I will follow the advice of the Lodash documentation, and use runInContext to avoid polluting Lodash with a new function that should remain local.

Here are my experiments. I took the liberty to rename some identifiers, as I hate the myXxx names and prefer more descriptive names...

// Generic code to display resuts

var results = document.getElementById('pls-results');
function showHTML(html)
{
  results.insertAdjacentHTML('beforeend', html);
}
function show(text)
{
  showHTML("<p>" + text + "<p>");
}
function showObject(obj)
{
  show("<p>" + JSON.stringify(obj) + "<p>");
}

// The real code

var keyList = [ 'a', 'b', 'c', 'd' ];
var incompleteJson = { "a": 1, "b": 2 };
var fullJson = { "a": 1, "b": 2, "c": true, "d": "yes" };

// A simple way to do what is implied by the variable name
showHTML("<h3>Has All Keys</h3>");

show("Incomplete Json");

var diff = _.difference(keyList, _.keys(incompleteJson));
var hasAllKeys = diff.length === 0;

show(hasAllKeys);

show("Full Json");

diff = _.difference(keyList, _.keys(fullJson));
hasAllKeys = diff.length === 0;

show(hasAllKeys);

// What you try to do
showHTML("<h3>Consume Expected Keys</h3>");

var localLodash = _.runInContext();
localLodash.mixin(
    {
        'reverseDiff': _.rearg(_.difference, [1, 0])  // switching order of arguments
    }
);

show("Incomplete Json");

var expectedKeys = localLodash.chain(incompleteJson)
     .keys()
     .reverseDiff(keyList)
     .value();

showObject(expectedKeys);

show("Full Json");

expectedKeys = localLodash.chain(fullJson)
     .keys()
     .reverseDiff(keyList)
     .value();

showObject(expectedKeys);
<div id="pls-results"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>

Upvotes: 2

Related Questions