I believe I have learned some/many/most of the basic concepts underlying functional programming in JavaScript. However, I have trouble specifically reading functional code, even code I've written, and wonder if anyone can give me any pointers, tips, best practices, terminology, etc. that can help.
Take the code below. I wrote this code. It aims to assign a percent similarity between two objects, between say {a:1, b:2, c:3, d:3}
and {a:1, b:1, e:2, f:2, g:3, h:5}
. I produced the code in response to this question on Stack Overflow. Because I wasn't sure exactly what kind of percent similarity the poster was asking about, I provided four different kinds:
- the percent of the keys in the 1st object that can be found in the 2nd,
- the percent of the values in the 1st object that can be found in the 2nd, including duplicates,
- the percent of the values in the 1st object that can be found in the 2nd, with no duplicates allowed, and
- the percent of {key:value} pairs in the 1st object that can be found in the 2nd object.
I started off with reasonably imperative code, but quickly realized that this was a problem well suited for functional programming. In particular, I realized that if I could extract out a function or three for each of the above four strategies that defined the type of feature I was seeking to compare (e.g. the keys, or the values, etc.), then I might be able to reduce (pardon the play on words) the rest of the code into repeatable units. You know, keeping it DRY. So I switched to functional programming. I'm pretty proud of the result, I think it's reasonably elegant, and I think I understand what I did quite well.
However, even having written the code myself and understanding every part of it during construction, when I now look back on it, I continue to be more than a little baffled at both how to read any particular half-line, as well as how to "grok" what any particular half-line of code is actually doing. I find myself making mental arrows to connect different parts that quickly degrade into a mess of spaghetti.
So, can anyone tell me how to "read" some of the more convoluted bits of code in a way that is both concise and that contributes to my understanding of what I'm reading? I guess the parts that get me the most are those that have several fat arrows in a row and/or parts that have several parentheses in a row. Again, at their core, I can eventually figure out the logic, but (I hope) there is a better way to go about quickly and clearly and directly "taking in" a line of functional JavaScript programming.
Feel free to use any line of code from below, or even other examples. However, if you want some initial suggestions from me, here are a few. Start with a reasonably simple one. From near the end of the code, there's this that is passed as a parameter to a function: obj => key => obj[key]
. How does one read and understand that? A longer example is one full function from near the start: const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));
. The last map
part gets me in particular.
Please note, at this point in time I'm not looking for references to Haskell or symbolic abstract notation or the fundamentals of currying, etc. What I am looking for is English sentences that I can silently mouth while looking at a line of code. If you have references that specifically address exactly that, great, but I'm also not looking for answers that say I should go read some basic textbooks. I've done that and I get (at least a significant amount of) the logic. Also note, I don't need exhaustive answers (although such attempts would be welcome): Even short answers that provide an elegant way of reading a single particular line of otherwise troublesome code would be appreciated.
I suppose a part of this question is: Can I even read functional code linearly, you know, left-to-right and top-to-bottom? Or is one pretty much forced to create a mental picture of spaghetti-like wiring on the page of code that is decidedly not linear? And if one must do that, we still have to read the code, so how do we take linear text and wire up the spaghetti?
Any tips would be appreciated.
const obj1 = { a:1, b:2, c:3, d:3 };
const obj2 = { a:1, b:1, e:2, f:2, g:3, h:5 };
// x or X is key or value or key/value pair
const getXs = (obj, getX) =>
Object.keys(obj).map(key => getX(obj)(key));
const getPctSameXs = (getX, filter = vals => vals) =>
(objA, objB) =>
filter(getXs(objB, getX))
.reduce(
(numSame, x) =>
getXs(objA, getX).indexOf(x) > -1 ? numSame + 1 : numSame,
0
) / Object.keys(objA).length * 100;
const pctSameKeys = getPctSameXs(obj => key => key);
const pctSameValsDups = getPctSameXs(obj => key => obj[key]);
const pctSameValsNoDups = getPctSameXs(obj => key => obj[key], vals => [...new Set(vals)]);
const pctSameProps = getPctSameXs(obj => key => JSON.stringify( {[key]: obj[key]} ));
console.log('obj1:', JSON.stringify(obj1));
console.log('obj2:', JSON.stringify(obj2));
console.log('% same keys: ', pctSameKeys (obj1, obj2));
console.log('% same values, incl duplicates:', pctSameValsDups (obj1, obj2));
console.log('% same values, no duplicates: ', pctSameValsNoDups(obj1, obj2));
console.log('% same properties (k/v pairs): ', pctSameProps (obj1, obj2));
// output:
// obj1: {"a":1,"b":2,"c":3,"d":3}
// obj2: {"a":1,"b":1,"e":2,"f":2,"g":3,"h":5}
// % same keys: 50
// % same values, incl duplicates: 125
// % same values, no duplicates: 75
// % same properties (k/v pairs): 25