9

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
18

You're mostly having difficulty reading it because this particular example isn't very readable. No offense intended, a dishearteningly large proportion of samples you find on the Internet aren't either. A lot of people only play around with functional programming on the weekends and never really have to deal with maintaining production functional code long term. I would write it more like this:

function mapObj(obj, f) {
  return Object.keys(obj).map(key => f(obj, key));
}

function getPctSameXs(obj1, obj2, f) {
  const mapped1 = mapObj(obj1, f);
  const mapped2 = mapObj(obj2, f);
  const same = mapped1.filter(x => mapped2.indexOf(x) != -1);
  const percent = same.length / mapped1.length * 100;
  return percent;
}

const getValues = (obj, key) => obj[key];
const valuesWithDupsPercent = getPctSameXs(obj1, obj2, getValues);

For some reason a lot of people have this idea in their head that functional code should have a certain aesthetic "look" of a big nested expression. Note although my version somewhat resembles imperative code with all the semicolons, everything is immutable, so you could substitute all the variables and get one big expression if you wanted. It's indeed just as "functional" as the spaghetti version, but with more readability.

Here the expressions are broken into very small pieces and given names that are meaningful to the domain. Nesting is avoided by pulling common functionality like mapObj into a named function. Lambdas are reserved for very short functions with a clear purpose in context.

If you come across code that is difficult to read, refactor it until it is easier. It takes some practice, but it is worthwhile. Functional code can be just as readable as imperative. In fact, often moreso, because it is usually more concise.

  • Definitely no offence taken! While I will still maintain that I know some things about functional programming, maybe my assertions in the question about how much I know were a little over-stated. I am really a relative beginner. So seeing how this particular attempt of mine can be re-written in such a concise clear but still functional way seems like gold...thank you. I'll be studying your re-write carefully. – Andrew Willems Feb 11 '17 at 12:35
  • 1
    I've heard it said that having long chains and/or nesting of methods eliminates unnecessary intermediate variables. In contrast, your answer breaks my chains/nesting into intermediate stand-alone statements using well-named intermediate variables. I find your code more readable in this case, but I'm wonder how general you're trying to be. Are you saying that long method chains and/or deep nesting are often or even always an anti-pattern to be avoided, or are there times when they bring significant benefit? And is the answer to that question different for functional versus imperative coding? – Andrew Willems Feb 13 '17 at 17:59
  • 3
    There are certain situations where eliminating intermediate variables can add clarity. For example, in FP you almost never want an index into an array. Also sometimes there isn't a great name for the intermediate result. In my experience, though, most people tend to err too far the other way. – Karl Bielefeldt Feb 13 '17 at 19:43
6

I've not done a lot of highly functional work in Javascript (which I would say this is -- most people talking about functional Javascript may be using maps, filters and reduces, but your code defines its own higher-level functions, which is somewhat more advanced than that), but I have done so in Haskell, and I think at least some of the experience translates. I'll give you a few pointers to things I've learned:

Specifying the types of functions is really important. Haskell doesn't require you to specify what the type of a function is, but including the type in the definition makes it much easier to read. While Javascript doesn't support explicit typing in the same way, there's no reason not to include the type definition in a comment, e.g.:

// getXs :: forall O, F . O -> (O -> String -> F) -> [F]
const getXs = (obj, getX) =>
    Object.keys(obj).map(key => getX(obj)(key));

With a bit of practice at working with type definitions like this, they make the meaning of a function much clearer.

Naming is important, perhaps even more so than in procedural programming. A lot of functional programs are written in a very terse style that's heavy on convention (e.g. the convention that 'xs' is a list/array and that 'x' is an item in it is very pervasive), but unless you understand that style easily I'd suggest more verbose naming. Looking at specific names you've used, "getX" is kind-of opaque, and therefore "getXs" doesn't really help very much either. I'd call "getXs" something like "applyToProperties", and "getX" would probably be "propertyMapper". "getPctSameXs" would then be "percentPropertiesSameWith" ("with" is another one of the conventions -- it states that a prefiltering function is applied before some other operation, cf the Haskell standard functions like zipWith).

Another important thing is to write idiomatic code. I notice that you're using a syntax a => b => some-expression-involving-a-and-b to produce curried functions. This is interesting, and could be useful in some situations, but you're not doing anything here that benefits from curried functions and it would be more idiomatic Javascript to use traditional multiple-argument functions instead. Doing so may make it easier to see what's going on at a glance. You're also using const name = lambda-expression to define functions, where it would be more idiomatic to use function name (args) { ... } instead. I know they're semantically slightly different, but unless you're relying on those differences I'd suggest using the more common variant when possible.

  • 5
    +1 for types! Just because the language doesn't have them, doesn't mean you don't have to think about them. Several documentation systems for ECMAScript have a type language for recording the types of functions. Several ECMAScript IDEs have a type language as well (and usually, they also understand the type languages for the major documentation systems), and they can even perform rudimentary type checking and heuristic hinting using those type annotations. – Jörg W Mittag Feb 11 '17 at 10:56
  • You've given me a lot to chew on: type definitions, meaningful names, using idioms...thank you! Just a few of many possible comments: I wasn't necessarily intending on writing certain parts as curried functions; they just evolved that way as I refactored my code during writing. I can see now how that wasn't needed, and even just merging the parameters from those two functions into two parameters for a single function not only makes more sense but instantly makes that short bit at least more readable. – Andrew Willems Feb 11 '17 at 12:44
  • @JörgWMittag, thanks for your comments about the importance of types and for the link to that other answer you wrote. I use WebStorm and didn't realize that, according to how I read that other answer of yours, WebStorm knows how to interpret jsdoc-like annotations. I'm assuming from your comment that jsdoc and WebStorm can be used together for annotating functional, not just imperative, code, but I'd have to delve further to really know that. I have played with jsdoc before and now that I know that WebStorm and I can cooperate there, I expect I'll use that feature/approach more. – Andrew Willems Feb 11 '17 at 17:52
  • @Jules, just to clarify which curried function I was referring to in my comment above: As you implied, each instance of obj => key => ... can be simplified to (obj, key) => ... because later getX(obj)(key) can also be simplified to get(obj, key). In contrast, another curried function, (getX, filter = vals => vals) => (objA, objB) => ..., cannot be easily simplified, at least in the context of the rest of the code as written. – Andrew Willems Feb 12 '17 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.