I'll take a stab at explaining the reduce function, though it's quite possible my explanation will require more elucidation by someone more knowledgable... Primer: reduce() and _.reduce() You two probably don't need this, but for anyone else that might read and want to understand, think of reduce() as a snake eating its own tail, segment by segment. It passes over an array and, for each element, returns some piece of information that is fed to the reduce for the next element in the array. That returned piece of information is called the "memo", and it will obviously grow as the reduce function moves from element to element. This is different from, say, map() , which changes each element in the array in place, returning an array: let outputArray = inputArray.map(a=>a*2); /* produces the following process...
================================================
inputArray | map(a=>a*2) | outputArray
================================================ 1 | => | 2 2 | => | 4 3 | => | 6 4 | => | 8
*/
// outputArray === [2,4,6,8] By contrast, reduce() returns an object of the type of your memo. That means you can use reduce() to return a string, a number, or a more complex object. The call looks a lot like map() , except we need to include the memo argument (we'll use m ), and we have to supply an initial value for the memo for the first time through the loop (if yo don't supply a starting value, it will use the first item in the array). ┌───────────┐
v ^ let output = inputArray.reduce((m,a)=>m+a*2,0);
^ ^ ^ memo─┘ | └─initial value for memo
└─array element Given the same inputArray as the first example, m will return as 20, since it starts at 0 and adds 2x the value of each array element: let output = inputArray.reduce((m,a)=>m+a*2);
/* produces the process...
================================================
inputArray | reduce((m,a)=>m+a*2,0) | m
================================================ 1 | 0 + 1*2 | 2 2 | 2 + 2*2 | 6 3 | 6 + 3*2 | 12 4 | 12 + 4*2 | 20
*/
// output === 20 In that case, we fed in a number, and every time we returned that number, so our final output was a number. You can build an object by starting with an initialized object (a prefilled object or an empty object {}), then returning that object in the reduce callback. Index (or Key) - The other argument relevant to this explanation (as it was utilized in the referenced code) is the index of the array element. It would be the third argument, if included, so k in the following implementation: let output = inputArray.reduce((m,a,k) => {
m['Item' + k] = a;
return m;
}, {}); That utilizes the variable naming pattern for an object (obj[variable]), obtaining unique property names because each index is unique. At the end of that, output is an object with properties of Item0, Item1, Item2, etc. Underscore Version The underscore version of reduce() is very similar, except that (1) it works on objects as well as arrays (pure javascript function only works on arrays), and (2) it gets the list it should operate over passed to it as the first argument (before the callback). If the reduce() comes within the _.chain() block, it will grab the wrapped object by default: _.chain(list).reduce((m,v,k)=>{},{}); ...will operate on the list as returned by _.chain() . As Utilized In the referenced code, then, we have: msg.content = _.chain(msg.inlinerolls) .reduce(function(m,v,k){ m['$[['+k+']]']=v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); The first reduce() gets inlinerolls , which is an array, as wrapped by chain() . It builds an object (m) by using the index (k) of the item's position, referring to each by the variable-name pattern: m['$[['+k+']]']=v.results.total || 0; Each property of the memo object will have a value of either the total (if present) or 0. The returned object will look like: { '$[[0]]': 10, '$[[1]]': 14 // etc. } The second reduce() is where I think most people lose the train of what is happening... (and to be fair, I may not have the full/right of it). Since the second reduce() is tacked onto the _.chain() block, it receives the object we just built with the first reduce() as the source of the value (v) and key (k). The slight of hand comes in spotting that it is being fed the msg.content as the initial value of the memo even though it's reading from the object we've constructed. So when it performs the line: return m.replace(k,v); ...it is taking the key (the '$[[0]]') and replacing it with the value (10, from my example, just above). It is performing that replace() operation on the memo, which was initialized to be the msg.content (a string). So where it sees '$[[1]]' it replaces it with the total value of that roll. Then it returns the version of the memo (m) that has the replacement done, and proceeds to the next property/value of the list object. TL;DR version: the first reduce() builds an object out of the inlinerolls array that lets us (in the second reduce() function) perform a set of replacement operations on the msg.content . In the end, the value() function unwraps the chain() object and returns the value to the msg.content .