Hacker News new | ask | show | jobs
by fair24 3198 days ago
> Folding Promises in JavaScript

Or: How to make simple things complex and make a codebase a complete puzzle for those that come after you?

3 comments

I feel nothing has improved code readability like the recent mainstreaming of map/filter/fold/reduce and "const all the things". This type of code is so easy to follow, reason about and trivial to debug at every step, once you internalize the few primitive functions.

I don't think you need to necessarily memorize these transformation names, but writing these types of functions is all I seem to be doing these days, transforming one thing into another line for line.

I feel the code I wrote while on this bandwagon is the hardest to understand for others and for me myself today.

    pullAllBy(pluck(things, 'bar').map(compose(xor, lol, rofl)).reduce(differenceWith('id'))
Just write your transformations inline and go work on the next feature.
The trick is to break that (on the dots) into three const assignments with descriptive names. Practically self-documenting, easily debuggable.

If you don't use the builtin transformations, you'll just end up re-implementing them, poorly. And adding to the cognitive overhead with new concepts. And I have to read your code with a fine-toothed comb to ensure it's really side-effect free. I don't advocate turning everything into a named function as in your example though, short one-off functions should all be inline IMO.

I feel like the functional code you wrote was written to intentionally obfuscate what is being done. For instance, composing a bunch of methods inline doesn't have any utility for it unless you define what compose(xor, lol, rofl) really means.

Ideally this is how it should be written for maximum readability.

  things
  |> pluck('bar')
  |> map(xor)
  |> map(lol)
  |> map(rofl)
  |> reduce(differenceWith('id')

The code is written equally well without using pipe operator but the proposal to introduce it is in works [1].

Here is it using lodash (not even lodash-fp), and this is going to do a single for loop when executing because this is lazy.

  _.chain(things)
   .pluck('bar')
   .map(_.xor)
   .map(_.lol)
   .map(_.rofl)
   .reduce(_.differenceWith('id'))
   .value();
It's no less readable than the code you'd write using using unfolded transformations.

1. https://github.com/tc39/proposal-pipeline-operator

I agree, but you shouldn't map 3 times when you can map once over the data.

   things
    |> pluck('bar')
    |> map(compose(rofl, lol, xor))
    |> reduce(differenceWith('id')
I made my case against the compose in the post.

Composing xor, rofl, lol isn't any better (esp in terms of readability) than it is individual maps.

What would be better is this:

  const makeHilarious = compose(rofl, lol, xor);
  
  things
    |> pluck('bar')
    |> map(makeHilarious)
    |> reduce(differenceWith('id')
This means that to change this code you first have to look for the makeHilarious definition, then the definitions of rofl, lol and xor, then figure out what they all do separately and together, and if you can change them without breaking anything else in your application.

    things
      .map(thing => x.bar)
      .map(thing => {
        // whatever happens in rofl
        // whatever happens in lol
      })
      .reduce((acc, thing) => {
        // more stuff
      }, {})
This is code that is easy to understand and safe to change. The maps could be combined into one function body if it's convenient.
the nice thing is that you can easily split that line up as much as makes sense, should you decide you need to access some intermediate form of the data. and you can use the variable names as a comment that explains what that chunk of transformations represents.

so someone reading over it can kind of skim down the left side and follow what's happening and scan to the right if they need to understand some part in detail

If only you could still recognize it as a transformation when it is inline
These things map/reduce/compose/flatmap are universal - they're in java, python, c#, Ocaml, Haskell, lisp, ruby, javascript...

Complaining about learning them is like complaining about for loops. They just exist.

Just because some are more familiar with for loops than map doesn't mean that more universal, immutable, expression - based solutions are not widely familiar and easy to understand to programmers coming from other languages.

Readability is subjective.

I've been working with JavaScript since February and I want to give you a hug.