DEV Community

Discussion on: Please don't "overchain" array methods

Collapse
 
ardcore profile image
Szymon Pilkowski

I disagree with this article.
We shouldn't sacrifice readability and composability for performance gains, and we don't have to.

Our goal here is to iterate over things in a way that is both readable and performant. Let's have a look at different methods of achieving this.

We want to multiply stuff by 2, and then add 4. Let's define these goals as functions:

const mulByTwo = (n => n * 2);
const addFour = (n => n + 4);
Enter fullscreen mode Exit fullscreen mode

This, as mentioned in the article, is readable, but may become a performance issue:

[1, 2, 3].map(mulByTwo)
         .map(addFour);
Enter fullscreen mode Exit fullscreen mode

The proposed solution, however, is not very readable, and doesn't scale/compose well:

[1, 2, 3].map(n => n * 2 + 4);
Enter fullscreen mode Exit fullscreen mode

This uses composition, but is far from being readable because of the
reversed, "inside-out" order of execution

[1, 2, 3].map(n => addFour(mulByTwo(n)));
Enter fullscreen mode Exit fullscreen mode

Solution? Best of both worlds. Let's create a higher-order function which would allow us to pipe
other function calls in a nice, clean, left-to-right order:

const pipe = (fn, ...fns) => 
  (...args) => fns.reduce((acc, f) => f(acc), fn(...args));
Enter fullscreen mode Exit fullscreen mode

We can now do stuff!

[1,2,3].map(
  pipe(mulByTwo, addFour)
);
Enter fullscreen mode Exit fullscreen mode

In fact, multiple functional/semi-functional libraries have pipe function in their toolbelt. it's also a part of my very minimal fp library.
We can as well define all, some or none functions and use them to compose our filter logic.

Collapse
 
somedood profile image
Basti Ortiz

Now this is an elegant solution. The only problem with it is that it isn't exactly the most beginner-friendly code. You must have a significant amount of experience with functional programming to be able to fathom the notion of functions inside functions inside functions for composition.

Besides that, it really is an elegant solution that combines the best of both worlds. Pipes truly are a marvel of (software) engineering.

Collapse
 
okdewit profile image
Orian de Wit

isn't exactly the most beginner-friendly code. (...) Pipes truly are a marvel of (software) engineering.

Maybe it's because I started out learning programming from the FP side, but composition feels like pretty intuitive beginner stuff, and I've always had trouble reading ugly loops which store all kinds of stuff in variables.

I think "beginner friendly" highly depends on what you've been exposed to so far.

Thread Thread
 
somedood profile image
Basti Ortiz

Yuppp, loops are always ugly. There is no question about that. You have no idea how many times I've gotten myself inside infinite loops. It's just a horrible experience when I was a beginner.

And I think you're right about that. It does depend on what you've been exposed to as a beginner.

Thread Thread
 
zorqie profile image
I, Pavlov

This is a bit like saying that nails are always ugly, you should use screws. Beginners should learn when to use which, in addition to how not to hit their fingers.

FP is cool and all but FP in JS has become an unbearable infomercial lately: "Have you suffered from infinite loops? Ever feel like your code doesn't have enough dots or arrows? Tired of using plus signs for addition? Try FP"

Collapse
 
nogginbops profile image
Julius Häger • Edited

What is the performance of this solution?
Because this will be guaranteed slower than the solution in the artice because the JavaScript engine almost certainly doesn't do functional optimization. (+ You create a capture witch is slow, needs more optimization which adds to potential jit time)

It is also less simple as you have to understand more concepts to understand the code, especially because JS is an imperative language the functional style is a bad fit. In imparative languages a for-loop with the proper if-statements and calculations will always be the better option for both performance and simplicity.

Please don't bully my phone's battery more than JS already has to. :)

Collapse
 
qm3ster profile image
Mihail Malo

Funny, I usually define my pipe as following:

const pipe = (...fns) => arg => {
  let val = arg
  for (const fn of fns) {
    val = fn(arg)
  }
  return val
}

I also make args unary, so that I don't have to think about methods like map injecting spurious parameters.
Next comes giving it a TS type signature.
Which involves overloads.
It's horrible, but the results are great.