Most of what I will discuss in this article is knowledge accumulated from reading, “Functional Programming in JavaScript”, by Luis Atencio. Let’s dig right in…
In simple terms, functional programming is a software development style that places a major emphasis on the use of functions. You might say, “Well, I already use functions daily, what’s the difference?” Well, it’s not a matter of just applying functions to come up with a result. The goal, rather, is to abstract control flows and operations on data with functions in order to avoid side effects and reduce mutation of state in your application.
Abstract Control Flows
To understand what abstract control flows means, we need to first look at a model of programming called Imperative or Procedural Programming. Imperative programming treats a computer program as merely a sequence of top-to-bottom statements that change the state of the system in order to compute a result.
Imperative programming tells the computer, in great detail, how to perform a certain task (looping through and applying a given formula to each item in an array for instance). This is the most common way of writing code. Here is an example.
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for(let i = 0; i < array.length; i++) {
array[i]= Math.pow(array[i], 2);
}
array;
//-> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Functional programming, on the other hand, falls under the umbrella of Declarative Programming: it’s a type of programming that expresses a set of operations without revealing how they’re implemented or how data flows through them.
Declarative programming separates program description from evaluation. It focuses on the use of expressions to describe what the logic of a program is without necessarily specifying its control flow or state changes.
You only need to be concerned with applying the right behavior at each element and give-up control of looping to other parts of the system. For example, letting Array.map() do most of the heavy lifting in the task we did earlier, would be considered a more functional approach.
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(num => Math.pow(num, 2));
//-> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
With this approach, we abstract our loops with functions, and compared to the previous example, you see that this code frees you from the responsibility of properly managing a loop counter and array index access. Put simply, the more code you have, the more places there are for bugs to occur. Also, standard loops aren’t reusable artifacts unless they’re abstracted with functions.
Ideally, manual loops should be removed completely from your code in favor of first-class, higher-order functions like map, reduce and filter, which accept functions as parameters so that your code is more reusable, extensible, and declarative.
Avoid Side Effects
Functional programming is based on the premise that you use Pure Functions (functions with no side-effects) as building blocks when building your programs. Pure functions have the following characteristics:
They depend only on the input provided and not on any hidden or external input.They don’t inflict changes beyond their scope, such as modifying a global object.
Intuitively, any function that doesn’t meet these requirements is “impure.” Consider the following function:
var counter = 0;
function increment() {
return ++counter;
}
This function is impure because it reads/modifies an external variable, counter, which isn’t local to the function’s scope. Generally, functions have side effects when reading from or writing to external resources, as shown in the example above. Its result is unpredictable because the counter variable can change at any time between calls.
One might ask, “If you’re unable to create and modify objects or print to the console, what practical value would you get from a program like this?”, Indeed, pure functions can be hard to use in a world full of dynamic behavior and mutation. But practical functional programming doesn’t restrict all changes of state; it just provides a framework to help you manage and reduce them while allowing you to separate the pure from the impure. Impure code produces externally visible side effects.
Reduce Mutation of State
Immutable data is data that can’t be changed after it’s been created. In JavaScript, as with many other languages, all primitive types ( String, Number, and so on) are inherently immutable. But other objects, like arrays, aren’t immutable.
The state of a program can be defined as a snapshot of the data stored in all of its objects at any moment in time. Sadly, JavaScript is one of the worst languages when it comes to securing an object’s state. A JavaScript object is highly dynamic, and you can modify, add, or delete its properties at any point in time. Although this may give you the liberty to do many slick things, it can also lead to code that’s extremely difficult to maintain.
To step back a bit, you might ask why its recommended you always remove manual loops from your code? Well, a manual loop is an imperative control structure that’s hard to reuse and difficult to plug into other operations. In addition, it implies code that’s constantly changing or mutating in response to new iterations.
functional programs aim for statelessness and immutability as much as possible.
Stateless code has zero chance of changing or breaking global state. To achieve this, you’ll use functions that avoid side effects and changes of state, known as pure functions.
ES6 uses the const keyword to create constant references. This moves the needle in the right direction because constants can’t be reassigned or re-declared. In practical functional programming, you can use const as a means to bring simple configuration data ( URL strings, database names, and so on) into your functional program. But this doesn’t solve the problems of mutability to the level that FP requires. You can prevent a variable from being reassigned, but how can you prevent an object’s internal state from changing? For example, this code would be perfectly acceptable:
const student = new Student('Alonzo', 'Church', '666-66-6666', 'Princeton');
student.lastname = 'Mourning';
What you need is a stricter policy for immutability of which, Encapsulation is a good strategy to protect against mutations. For simple object structures, a good alternative is to adopt the Value Object pattern also referred to as the Module pattern. A value object is one whose equality doesn’t depend on identity or reference, just on its value; once declared, its state may not change.
function zipCode(code, location) {
let _code = code;
let _location = location || '';
return {
code: function () {
return _code;
},
location: function () {
return _location;
},
fromString: function (str) {
let parts = str.split('-');
return zipCode(parts[0], parts[1]);
},
toString: function () {
return _code + '-' + _location;
}
};
}
In JavaScript, you can use functions and guard access to a function’s internal state by returning an object literal that exposes a small set of methods to the caller and treats its encapsulated properties as pseudo-private variables. These variables are only accessible in the object literal via closures, as you can see in the example above.
The returned object effectively behaves like a primitive type that has no mutating methods. Hence, the toString method, although not a pure function, behaves like one and is a pure string representation of this object. Value objects are lightweight and easy to work within both functional and object-oriented programming.
When thinking about your application’s design, ask yourself the following questions:
If you answer yes to any of these questions, then, by all means, make functional programming your friend. Just remember,
In functional programming, functions are the basic units of work, which means everything centers around them.
A function is any callable expression that can be evaluated by applying the () operator to it. Functions can return either a computed value or undefined (void function) back to the caller. Because FP works a lot like math, functions are meaningful only when they produce a usable result (not null or undefined ) otherwise, the assumption is that they modify external data and cause side effects to occur.
In order to benefit from functional programming, you must learn to think functionally and as a result, develop your functional awareness — the instinct of looking at problems as a combination of simple functions that together provide a complete solution.
At a high level, functional programming is effectively the interplay between decomposition (breaking programs into small pieces) and composition (joining the pieces back together). It’s this duality that makes functional programs modular and so effective.
Previously published at https://medium.com/@phillaipmusiime/lets-talk-functional-programming-bc12d66d604d