DEV Community

Cover image for An Adequate Introduction to Functional Programming

An Adequate Introduction to Functional Programming

Andrea Bertoli on October 18, 2019

This article is part of a series where we explore functional and reactive programming both in general terms and applied to JavaScript. In this fi...
Collapse
 
philipstarkey profile image
Phil Starkey

I see something like this written in almost every article on functional programming:
"But, as you can see, the imperative code is verbose and not immediately clear. On the other hand, the declarative approach is readable and explicit, because it focuses on what we want to obtain."

Authors seem to treat it as objective fact that the declarative approach is clearer, and yet often I find myself able to be certain of what the code does (particularly with edge cases) when reading the imperative code, not the declarative code. Anyone with a familiarity of any programming language would be able to understand the imperative example, but I don't feel the same is necessarily true of the declarative example.

Is it just me, or are people who promote functional programming blinded by their experience and familiarity with that paradigm?

This is not to say one style is better than another, I just find such statements really jarring to read because it's not the case for me.

Collapse
 
mr_bertoli profile image
Andrea Bertoli

Your issue is about the concept of "abstraction", not really about paradigms. Indeed also OOP hides imperative code.

Let's put it in another way: the imperative approach exposes you to the implementation of some task. But it doesn't communicate clearly and immediately what's the purpose of your code.

When you find yourself in front of 1000+ lines of imperative code (maybe written by someone else) you'll only see a long block/blob of low-level code (until you decode it). This leads often to lose sight of the "why" or "what" the code is trying to accomplish.

Sometimes you don't even need to know the actual implementation. Often because is crystal clear what the function is supposed to do, or because a simple analysis of inputs/outputs will clarify the operation performed.

In any case, functions/classes/modules etc. are written by someone, so when needed you can dive into the actual implementation. Same for built-in functions: the specs are clear about what they do and how.

So the point here is: the declarative style/code abstract away from low-level stuff (zoom out) so you can understand what's the goal/direction of the code in front of you. But you can always dig into the actual implementation if you need/want.

Good point, though!

Collapse
 
eaich profile image
Eddie

Agreed. Imperative was clearer to me in these examples. I use a combo of programming paradigms personally. Depends on the the situation.

The FP vs. OOP dichotomy is silly. They are and can be compatible with each other. In general, I take advice that suggests to use X in every situation with a grain of salt.

Collapse
 
mr_bertoli profile image
Andrea Bertoli

Assuming a little bit of JS knowledge the declarative approach is absolutely clear.

Of course to appreciate the declarative style you have to know the language or the used functions.

Anyway, as I said in the article,there's no best choice. Any situation needs the right approach.

Collapse
 
vedgar profile image
Vedran Čačić

Of course. Every programming paradigm is clear to the person who internalized it. Same as with natural languages -- yes, there are a few "universal idioms" common to all (or most), but almost everything is based on familiarity, not on some high clarity independent of experience.

Just to show how easy it is to make this mistake: you make it too. :-P Look:

Anyone with a familiarity of any programming language would be able to understand the imperative example

Of course this is not true. There are many functional programming languages, and more and more people are starting with them, since more and more schools are realizing the benefits of FP (again, those benefits are not some lofty clarity ideals, but practical things like easier piecewise testing, and thus easier grading;). But I completely understand that it seems this way to you, precisely because your first experience is with imperative programming.

Collapse
 
aleksandar874 profile image
aleksandar87

I do this all the time but problem is that by time I get lot of this helpers scatter around the code base

Collapse
 
aminnairi profile image
Amin • Edited

Hi there, great article!

Just to be sure everyone is on the same page for the pipe function, I rewrote it using the function keyword and a for...of loop (and human-readable variable names).

"use strict";

function pipe(...functions) {
    // functions === [increment, double, square]

    return function(value) {
        // value === 1

        let finalValue = value;

        for (const getFunctionReturnValueFor of functions) {
            finalValue = getFunctionReturnValueFor(finalValue);
            // finalValue === increment(1) === 2
            // finalValue === double(2) === 4
            // finalValue === square(4) === 16
        }

        return finalValue;
        // finalValue === 16
    }
}

function increment(value) {
    return value + 1;
    // 1 + 1 === 2
}

function double(value) {
    return value * 2;
    // 2 * 2 === 4
}

function square(value) {
    return value ** 2;
    // 4 ** 4 === 16
}

const incDoubleSquare = pipe(increment, double, square);

console.log(incDoubleSquare(1)); // 16
Enter fullscreen mode Exit fullscreen mode

This is just an imperative way of writing what Andrea wrote with the reduce method. Same thing, different way of writing it. Maybe a little more understandable for newcomers with the imperative way. I hope that helped!

Collapse
 
uzitech profile image
Tony Brix

Seems kind of ironic that someone would need to write code in an imperative way to make it more readable in an article that claims declarative programming is easier to read. 😂🤣

Collapse
 
aminnairi profile image
Amin

You are correct! I think that when beginners start to see things like map or reduce or findIndex they get lost quicker than when using this way of writing (which uses concepts that are present in other imperative languages). Maybe a little more approachable to begin the smooth transition to the Array methods.

Thread Thread
 
mr_bertoli profile image
Andrea Bertoli • Edited

Thanks Amin for the "human-readable" version of pipe! 😁

The article supposes some degree of familiarity with JS. Maybe I need to mention some prerequisites in the intro part.

Collapse
 
Sloan, the sloth mascot
Comment deleted
 
aminnairi profile image
Amin

You are very welcome friend. I also understand that this topic could last for hours of reading but I think we can agree that we agree. 😂

Anyway great article again I really enjoy reading this through. Keep up the good work!

Collapse
 
shaibuzach profile image
Shaibu Zachariyya

Thank you.

Collapse
 
stefant123 profile image
StefanT123

Objects and arrays are passed by reference in JS, that is, if referenced by other variables or passed as arguments, changes to the latter ones affects also the originals. Sometimes copying the object in a shallow way (one level deep) is not enough, because there could be internal objects that are in turn passed by reference.

This is not entirely correct.

In Javascript primitives and objects are always called by value, and the value that is passed is a reference. This means that the reference to an Object is copied and passed as a value.

Collapse
 
jeromedeleon profile image
Jerome De Leon

This is great article but if you care about performance,
Using this approach is much more appropriate because you only loop once
and not creating new reference for every method you use.

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    if (numbers[i] < 5) {
      result.push(5)
      continue
    }
    result.push(numbers[i])
  }
}
Enter fullscreen mode Exit fullscreen mode

Unlike this one, although much more readable, but every function creates its own loop.

numbers
  .filter(n => n % 2 === 0)
  .map(n => n < 5 ? 5 : n)
Enter fullscreen mode Exit fullscreen mode

Correct me if I'm wrong here 😁.
It's just that I prefer performance rather than readability.

Collapse
 
mr_bertoli profile image
Andrea Bertoli

Thanks for this comment 😁, but I totally disagree with you.

Thinking about performance before profiling you code is called "premature optimization" which is a known to be a bad thing in software development, also because without appropriate tests, you don't even know where the "bottlenecks" really are.

Furthermore, 90% of the time you don't have performance problems nor this type of operations leads to performance issues (unless you work with long long long arrays). Also, often performance problems are present due to bad software architecture.

Finally, if you want really performant code maybe you need to consider other solutions than JS 😅

Collapse
 
jeromedeleon profile image
Jerome De Leon

Thanks. I'd look into that.

Collapse
 
pobch profile image
Pob Ch

Great article!
I noticed some typo in the article. You use 'sub' as an argument name but in the function body you use 'user' instead.

Collapse
 
mr_bertoli profile image
Andrea Bertoli

Thanks! Fixed!

Collapse
 
mzahraei profile image
Ardalan

Great Artikel

Collapse
 
mr_bertoli profile image
Andrea Bertoli

Thank you!

Collapse
 
vagoel profile image
Varun

Cannot agree more."Pipe", "Compose" utils are mind blowing.