DEV Community

Cover image for Is functional programming in JS really worth it?
Ivan Spoljaric
Ivan Spoljaric

Posted on

Is functional programming in JS really worth it?

I admit that I am still at the beginning of my FP journey, but it's examples like these that make me sceptical about the whole FP hype which is consuming the JS world.

Imperative isOdd:

const isOdd = n => n%2 === 1;
Enter fullscreen mode Exit fullscreen mode

Imperative isEven:

const isEven = n => n%2 === 0;
Enter fullscreen mode Exit fullscreen mode

Declarative / FP isOdd:

// utilities
const mod = y => x => x % y;
const eq = y => x => x === y;

const mod2 = mod(2);
const eq1 = eq(1);

const compose = (fn2, fn1) => v => fn2(fn1(v));

// end result
const fpIsOdd = compose(eq1, mod2);
Enter fullscreen mode Exit fullscreen mode

Declarative / FP isEven:

// utilities
const not = fn => x => !fn(x);

// end result
const fpIsEven = not(fpIsOdd);
Enter fullscreen mode Exit fullscreen mode

The FP style may be more readable (or is it?), but I needed to write 5-6 utility functions to achieve the same end result, so what are the real benefits here?

I wouldn't even know how I could justify writing code like this to my team members.

I am already used to writing FP code in moderation (in my JS/TS projects), like

  • using .map, .reduce, .filter
  • constructing pure functions
  • using redux for global state management (store, pure reducers, actions)
  • doing shallow/deep copying of function params when needed (to minimise mutating reference variables outside of the function's lexical scope)

And implementing these concepts doesn't require so much overhead in code. To be fair, redux does add a layer of indirection but it's totally manageable and justified.

But the more I learn about advanced FP concepts like pointfree style, composition and piping, the more absurd it seems to use it in my projects.

So for me the jury is still out on this one

Maybe I just can't see the grand and majestic FP forest because of all the composition/piping trees that I'm currently surrounded with.

What do you think about FP? Do you have any experience in using the more advanced techniques in some serious JS/TS projects?

Top comments (15)

Collapse
 
ahferroin7 profile image
Austin S. Hemmelgarn

First off, there is nothing in FP that says you have to decompose every single operation to the greatest degree possible and make everything operate in a curied manner. This is simply stuff that functional purists want, but a lot of it actually doesn’t have much practical benefit to developers or users.

Composition is useful in a number of cases, but it becomes visibly less practical the smaller your functional units are. Same for the concept of pipelines.

As an easy example, the following two samples of Elixir code are functionally identical, but the second is far more readable for most people (the |> operator in Elixir takes the value on the left side and passes it as the first argument to the function on the right, evaluating left to right as it goes):

x = f(g(h(y)))
Enter fullscreen mode Exit fullscreen mode
x = y |> f() |> g() |> h()
Enter fullscreen mode Exit fullscreen mode

Of course, you could also do the same thing with temporary variables (and that would be the norm in many languages that have mutable data types), but that requires you as the developer to keep track of more things. And, again, this looks impractical to some because of how short the sequence is, but once you have a pipeline a dozen or more functions long, or all the parts of the pipeline take anonymous functions in addition to the starting argument, it makes things visibly less cleaner.

Collapse
 
stereobooster profile image
stereobooster

Isn't it other way around?

x = f(g(h(y)))
x = y |> h() |> g() |> f()
Enter fullscreen mode Exit fullscreen mode

|> operator is called pipe, you can construct similar function in JS

Collapse
 
ahferroin7 profile image
Austin S. Hemmelgarn

Quite so. I’m obviously not used to writing pipes all on one line in Elixir (though that kind of makes sense, as the main places where this gets used are longer sequences of calls with multiple arguments to the individual functions, and there it’s preferred to have a newline before each pipe operator and have them all lined up under the initial value being operated on).

Collapse
 
bradtaniguchi profile image
Brad

I'm in the camp that believes in balance.

Functional Programming can be very useful in some cases. That doesn't mean you should 100% buy in and become a purist, as there are always trade offs. Its undeniable that Functional Programming isn't new, can get complicated or confusing, and didn't catch on for a reason... 40 years ago.

I think its false belief that you have to 100% buy into functional programming to make it work and be useful for you. As you pointed out there are some very useful ways to leverage functional programming here and there.

So yes "beware the hype", always beware the hype, and focus more on the underlying content, and less on what people say about the underlying content ;D

Collapse
 
stereobooster profile image
stereobooster

Its undeniable that Functional Programming isn't new, can get complicated or confusing, and didn't catch on for a reason... 40 years ago.

Why Isn't Functional Programming the Norm? – Richard Feldman

Collapse
 
bradtaniguchi profile image
Brad

I don't have time, or want to watch a 46 min video from the co-creator of Elm (a functional programming language) presented at a functional programming conference (Clojture).

Without watching any more than the first few frames of the video, I'd like to assume he will talk highly of functional programming, while including some historical context about why it isn't the current norm. He also may even push for the idea that it will become more popular in the future. Finally he may even will sprinkle in talk about how Elm is the future, by talking about how it solves the problems presented earlier in the talk.

I'd expect a person who made a functional programming language to say as such, otherwise why are they at the functional programming conference.

I may watch it in the future out of curiosity, but if conference talks are about hype, its important to pay attention to the source before buying into said hype. Its very possible, that person talking has some biases.

Thread Thread
 
dannymcgee profile image
Danny McGee

FWIW, I've watched the video and it's actually pretty interesting. Definitely some pie-in-the-sky evangelism about FP (though not about Elm in particular from what I remember), but mostly just a lot of history. (I don't really have a dog in the fight, for reference. I program mostly in TypeScript and mostly with classes, but I have taken a recent liking to pipe transforms thanks to RxJs.)

Collapse
 
dannymcgee profile image
Danny McGee • Edited

FP doesn't mean you're not allowed to string two tokens together unless they're functions. :P You have to compare apples to apples. Let's take an array of words, transform it into an array of numbers (where each number is the number of letters in that word), filter out the numbers that are odd, add all the numbers together.

This is imperative:

let result = 0;
for (let word of words) {
  let length = word.length;
  if (length % 2 === 0) {
    result += length;
  }
}
return result;
Enter fullscreen mode Exit fullscreen mode

This is functional:

return words
  .map(word => word.length)
  .filter(length => length % 2 === 0)
  .reduce((accum, current) => accum + current);
Enter fullscreen mode Exit fullscreen mode

Technically that's OO, since we're using methods on the Array object, but this is fundamentally an FP-style operation: it's declarative, it doesn't mutate anything, and there's no control structures. You could write the same thing with pure, free functions in a pipe instead of the method chaining. Here's what it would look like with lodash/fp:

return flow(
  map(word => word.length),
  filter(length => length % 2 === 0),
  reduce((accum, current) => accum + current)
)(words);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
stereobooster profile image
stereobooster

Several points:

I wouldn't even know how I could justify writing code like this to my team members.

Code should be readable by the team. Think of how much time you spend learning FP concept. The same amount of time would need to spend each team member...

You can learn and practice FP (in personal projects), but I would not put it in shared code base unless the whole team is on board.

Second: it depends how you define FP, but if we would define FP as the style programming which avoids mutations and side effects. You would see that curried functions and combination is not a goal by itself it's just a tool to achieve the goal. So you can use them or not as long the main goal achieved.

Third: JS doesn't have good support for FP. It has some bits, which would make your code look like FP, but not always give you the benefit of FP.

There are a lot of built in functions, which will mutate objects, for example RegExp.prototype.exec() or Array.prototype.splice(). There are no immutable data structures, so you would need to use something like immer to take care of that (or use copy with spreds).

What comes at no cost in FP languages, like auto currying or combination, you would need to implement or use library for it, like, rambda.

You can as well try to learn FP in a language which was designed for it, for example, Elm.

Collapse
 
jejones3141 profile image
James Jones

What is "imperative" about those first two functions? They don't modify their arguments or count on some global state. They'll always return the same value given the same input.

Collapse
 
ispoljari profile image
Ivan Spoljaric • Edited

Yeah, I agree with you. But the characteristics you defined make them pure. And just because they are pure it doesn't mean that they are automatically written in a declarative style.

In the purist FP way, as I understand it, functions should be written in point-free style to achieve the "declarative" data flow.

Collapse
 
vonheikemen profile image
Heiker • Edited

I'm also on a journey to learn functional programming. For me it is worth it because it "fits better" with the way I want to write code and think about problems. Using concepts and patterns from functional programming I can solve a problem one function at a time, that's really powerful. Now, I personally don't go "full functional" with javascript because you can get to a point where applying a "functional pattern" doesn't feel natural, sometimes the best and fastest way to solve something is the good ol' procedural/imperative style.

I haven't done any serious projects with javascript but I did make an experiment in one of my side projects where I tried to use as many things from functional programming without it being awkward. I really liked the result.

Collapse
 
taufik_nurrohman profile image
Taufik Nurrohman

Functional programming in JavaScript is optimal for production code e.g. when minified inside IIFE, because function name can be renamed. You cannot minify object property.

isOdd(x)
Enter fullscreen mode Exit fullscreen mode

vs.

x.isOdd()
Enter fullscreen mode Exit fullscreen mode
Collapse
 
trequetrum profile image
Trequetrum

I've listened to a few talks and read a book or two about functional programming. I would hope it's not a surprise that there isn't really a single definition of FP.

The one thing, however, that I've never come across is that point-free (tacit programming) is somehow more functional. It's sometimes more declarative, sure.

Consider that method chaining in OO languages is a form of tacit programming as well:

oby = oby'.addValue(5)
  .setPrecident(true)
  .prefixWith("hello");
Enter fullscreen mode Exit fullscreen mode

This isn't more functional than the equivalent:

oby1 = oby'.addValue(5)
oby2 = oby1.setPrecident(true)
oby = oby2.prefixWith("hello")
Enter fullscreen mode Exit fullscreen mode

You can see how a tacit style is sometimes easier to read, but it isn't more functional

Sometimes, however, it can be more efficient. These two functions are almost the same:

const thing1 = increment;
const thing2 = n =>  increment(n);
Enter fullscreen mode Exit fullscreen mode

thing1 and thing2 are both functions that increment their input. thing2, however, needlessly wraps this in a lambda. So thing2 performs ever so slightly worse as it must push and pop from the call stack one extra time.

Collapse
 
joshuakb2 profile image
Joshua Baker

Every code snippet in this article is functional. The only difference between your imperative and your functional examples is that the functional examples are convoluted.

isEven and isOdd are honestly such small tasks that it's hard to implement them in an imperative way. After all, imperative programming is about having a sequence of statements that have side effects, such as mutating variables.