DEV Community

George Rodier
George Rodier

Posted on • Updated on

Array Methods: The Gateway to Functional Programming

Functional programming seems to be catching on within the JavaScript community thanks to frameworks such has React and Redux. Having more readable and testable code is a no brainer. But words like functors, currying, higher order functions, and monads can be intimidating to someone looking to jump in. Luckily, you don't need to know everything to get started!

My favorite thing about JavaScript as a programming language is that it is multiparadigm. It supports working with an object oriented paradigm through prototypical inheritance and factory functions. This is what most JavaScript programmers are familiar with. However, functions are also first-class citizens in JavaScript. This means a function can act like any other object. They can be assigned to variables, passed in as an argument to a function, and even returned as a value from a function. This is important because it means functional programming is also supported in JavaScript.

The best part about JavaScript supporting object oriented and functional programming paradigms is they are not mutually exclusive. You can mix and match depending on your goals. This will also allow you to dip your toes into the functional world without having to fully commit. By focusing on data manipulation and working with array methods, you can develop a solid functional foundation on which to build upon.

Before jumping into data manipulation and array methods, I wanted to provide a brief definition of functional programming from Eric Elliott's blog series on composing software:

Functional programming is the process of building software by composing pure functions, avoiding shared state, mutable data, and side-effects. Functional programming is declarative rather than imperative, and application state flows through pure functions.

The definition contains a couple of key topics to functional programming such as pure functions, immutability, declarative style, and composition that will come up when exploring data manipulation and array methods more in depth.

Manipulating Data as a Starting Point

This may be an oversimplification, but programming involves doing three different things: acquiring input, manipulating data, and posting output. The input could be the result of reading from a database, a user triggering an event, or an API call. Likewise, posting output could be to an API, a new file, or manipulating the DOM.

Part of functional programming is eliminating side effects from your functions. A side effect is anything that manipulates some sort of state or variable outside it's own scope.

var x;
function addToX() {
  return x + 2;
}

function addTwo(n) {
  console.log(n + 2):
  return n + 2;
}
Enter fullscreen mode Exit fullscreen mode

In the function addToX there is a clear side effect as x is modified outside the scope of the function. However, in addTwo, console.log is also a side effect, because there is an observable change (something being printed to the screen) happening outside the scope of what is returned from the function. In fact, any function that calls a function with a side effect is also said to have a side effect.

Side effects can make posting data very difficult to reason about from a functional perspective. However, manipulating data shouldn't have any side effects. You get an input, you do something with that input, and you return an output. Given the same set of inputs, the same outputs should always be produced. If you can do that and not produce any side effects, your functions doing the data manipulation are said to be pure functions and you'll be hitting on another primary pillar of functional programming!

Manipulating Data with Array Methods

Manipulating data usually involves iterating over something, modifying data, filtering out unnecessary data, or transforming data to a different shape. Many times, this is achieved through for loops such as the ones seen below.

// transform each item in an array
var newArray = [];
for (var i = 0; i < oldArray.length; i++) {
  var newValue = oldArray[i] + 2;
  newArray.push(newValue);
}

...

// filter out select values
var newArray = [];
for(var i = 0; i < oldArray.length; i++) {
  if (oldArray[i] % 2 === 0) {
    newArray.push(oldArray[i])
  }
}

...

// transform data from array to string
var myCatString = "";
for(var i = 0; i < oldArray.length; i++) {
  var seperator = myCatString ? "," : "";
  myCatString += `Cat ${oldArray[i]}${seperator}`;
}
Enter fullscreen mode Exit fullscreen mode

The for loops above are just a couple small examples of what can be done when iterating through a set of data. However, there's a problem. If I didn't leave the comment before each code block, you'd have to read through each line of the for loop to understand what it's doing. The above examples may be easy enough to understand, but you'll often find different approaches are combined. There may even be for loops within for loops filled with conditionals. If that sounds confusing, that's because it is, and trying to go back and decipher what that block of code is doing can be difficult.

And that's not the only problem. The above example was careful not to modify the original array. However, there is no promise that will be true in a given for loop. I've read many for loops where they modify data outside their scope. This can lead to another set of bugs that is difficult to track down.

Luckily you can solve these issues by using array methods!

var newArray = oldArray.map(item => item + 2);

...

var newArray = oldArray.filter(item => item % 2 === 0);

...

var myCatString = oldArray.reduce((newStr, item) => {
  var seperator = newStr ? "," : "";
  return `${newStr}${seperator}`;
}, "")
Enter fullscreen mode Exit fullscreen mode

Each of the following examples is the same as the for loops above. However, by using map, filter, and reduce, I am being clear about the intention of the iterations. I can quickly see what a loop is trying to achieve without having to read through each line. They are mapping over some value to transform it, filtering to a smaller list, or reducing to another object shape. These array methods are said to be declarative as they describe what they are doing (without the need for a flow of control). This contrasts with an imperative style that is more procedural and describes how things are done.

Another benefit is that these array methods will return a new object (often a new array). In none of the examples am I modifying any pre-existing data. As a result, I am honoring the functional concept of immutability. Immutability means that once an object is created, it can't be modified in any way. By keeping your structures immutable, you help to ensure that your functions stay pure and you don't introduce any side effects.

Map, filter, and reduce aren't the only array methods you can use. There are tons others that you can apply as well. Be sure to check out the documentation to learn more and see the browser support for the various methods.

Point Free Style, Closures, and Currying with Array Methods

An important thing to take note of is that each array method takes in a function as an argument. This is a clear demonstration as a function as a first-class citizen. So, let's rewrite the functionality of our map iteration to use a reusable function.

function addTwo(n) {
  return n + 2;
}

oldArray.map(n => addTwo(n));
Enter fullscreen mode Exit fullscreen mode

One thing you'll find often in functional programming is something called point free style. Point free doesn't actually refer to the dot operator when accessing a property on an object, but rather the arguments to a function and not writing them where possible. If a function is an argument to another function and the parameters match up (in number and type), you don't need to pass in the arguments. In the previous example, addTwo and the anonymous function passed into map have the same parameters. In this case, you would only need to pass in addTwo without the arguments. The anonymous function isn't needed.

function addTwo(n) {
  return n + 2;
}

oldArray.map(addTwo);
Enter fullscreen mode Exit fullscreen mode

By taking advantage of a point free style you can have code that is even more terse and declarative. However, what can you do in the case where the parameters don't match up? Assume you still wanted to add two, but you only had an add function that took in two arguments. Since the parameter list does not match up you can't use a point free style.

function add(n, m) {
  return n + m;
}

oldArray.map(n => add(n, 2));
Enter fullscreen mode Exit fullscreen mode

Now you might be looking at that and thinking, is there a way I can apply the two beforehand, so I can use a point free style? And this is where higher order functions coupled with closure comes into play. A higher order function is any function that either takes in or returns another function.

So, the goal is going to create a new function that takes in a function and some arguments and returns a new function with those arguments partially apply applied.

function partiallyApply(fn, ...firstArgs) {
  return function(...remainingArgs) {
    return fn(...firstArgs, ...remainingArgs);
  };
}

var partialAdd = partiallyApply(add, 2);
oldArray.map(partialAdd);
Enter fullscreen mode Exit fullscreen mode

The function partiallyApply takes in a function and a list of initial arguments and returns a new function that will take in any remaining args. The initial function and firstArgs are saved because they are closed over by the lexical scope of the returning function. The inner workings of closure and scope deservers an entire article to itself.

You can then use the partiallyApply function to create a new partialAdd function that already has one of the arguments already applied. As a result, you are now able to line up our parameters and use a point free style!

The partiallyApply function, as its name might suggest, is an example of a partial application. Most functional libraries will already have this implemented for you. Very similar and related is currying. Both currying and partial application take a function and create a more specific function to use. While partial application takes a function and returns a new function with a reduced number of args, currying will create a chain of new functions that each take in one argument. Currying can also be used as a way of creating a more specific function to utilize the point free style in a similar way. The following uses a currying function as it would be seen in a functional library.

var add2 = _.curry(add)(2);
oldArray.map(add2);
Enter fullscreen mode Exit fullscreen mode

Chaining and Composing

A fun side effect of map (and some other array methods returning a new array), is that you can chain multiple array methods together.

[0, 1, 2, 3, 4].filter(isOdd).map(multiplyByTwo);
// [2, 6]
Enter fullscreen mode Exit fullscreen mode

In this example, filter will iterate over each item in the initial array and add it to a new array if the item satisfies the condition in the function passed into filter. The map function will then be called on the new array returned from filter, iterate through each of those items and perform the action described in the function passed into map. The result of map will be yet another new array being returned. The initial array is never modified.

Knowing that you can chain things together, you might get the idea that you can have multiple maps that each transform the data in the array in some way. This may even look declarative:

oldArray.map(addTwo).map(multiplyByThree);
Enter fullscreen mode Exit fullscreen mode

However, there is a problem here. You are now doing multiple iterations when only one is needed. Wouldn't it be nice if you could apply both mapping transformations in the same loop? Well you can! And it's easy as composing two functions together.

A function is just a building block of a larger program. Often you'll see the output of one function become the input of another function. In this case you can create a new function that is the composition of the other two functions.

function addTwo(x) {
  return x + 2;
}

function mutliplyByThree(x) {
  return x * 3;
}

var addTwoMultiplyByThree = _.compose(
  multiplyByThree,
  addTwo
);

var num = addTwoMultiplyByThree(4);
// num == 18
Enter fullscreen mode Exit fullscreen mode

The compose function takes two or more functions and returns a new function. In the case above, when the new function, addTwoMultiplyByThree, is called, it sends it's input to the addTwo function. The output of addTwo becomes the input of multiplyByThree and so on. You can visualize the composition similar to this:

multiplyByThree(addTwo(4)));
Enter fullscreen mode Exit fullscreen mode

Because of this composition, you can rewrite the double map function to use one iteration instead:

var addTwoMultiplyByThree = _.compose(
  multiplyByThree,
  addTwo
);

oldArray.map(addTwoMultiplyByThree);
Enter fullscreen mode Exit fullscreen mode

As you get comfortable with function composition, you'll learn that it is the foundation of any functional program. You'll take small reusable pieces of functionality and compose them together to larger pieces. Those larger pieces can also be composed with other larger pieces. And before you know it you have an entire application to manipulate data in different ways.

Starting Small to Learn Big

By working with array methods you'll be taking the initial baby steps to learning a lot that functional programming has to offer from pure functions, immutability, composition, declarative style, and even point free style, currying, partial application, and higher order functions. And this was accomplished without referring to any big terms such as functors or monads. Though believe it or not, you already were using functors throughout (though I'll leave that up to you to learn for now).

The goal is not to pick up everything at once. Rather, the takeaway should be to start using array methods when manipulating data and seeing how you can learn functional concepts from it. Some may argue array methods do not follow a pure functional style, but by starting small and using them now, you can make big gains as a developer. And hopefully as you see improvements, you can use that as a gateway to learning more about functional programming and how to apply it to your code.

Oldest comments (0)