This post is the beginning of a new series about functional programming called Thinking in Ramda.
I’m going to stick to the lighter, less-academic end of functional programming. This is mostly because I want to keep the series accessible for more people, but also partly because I’m not very far down the functional road myself.
In Using Redux-api-middleware With Rails, I used Ramda to help transform request and response payloads.
If you’d like to experiment with Ramda while reading through this series, there’s a handy in-browser sandbox on the Ramda site.
As the name might suggest, functional programming has a lot to do with functions. For our purpose, we will define a function as a reusable piece of code that is called with zero or more arguments and returns a result.
With ES6 arrow functions, you can write the same function much more tersely. I mention this now, because we’ll be using a lot of arrow functions as we go along.
Almost every language has some kind of support for functions.
Some languages go further and provide support for functions as first-class constructs. By “first-class”, I mean that functions can be used in the same way as other kinds of values. You can:
- refer to them from constants and variables
- pass them as parameters to other functions
- return them as results from other functions
When writing functional programs, it eventually becomes important to work mostly with so-called “pure” functions.
Pure functions are functions that have no side-effects. They don’t assign to any outside variables, they don’t consume input, they don’t produce output, they don’t read from or write to a database, they don’t modify the parameters they’re passed, etc.
The basic idea is that, if you call a function with the same inputs over and over again, you always get the same result.
You can certainly do things with impure functions (and you must, if your program is going to do anything interesting), but for the most part you’ll want to keep most of your functions pure.
Another important concept in functional programming is that of “immutability”. What does that mean? “Immutable” means “unchangeable”.
When I’m working in an immutable fashion, once I initialize a value or an object I never change it again. That means no changing elements of an array or properties of an object.
If I need to change something in an array or object, I instead return a new copy of it with the changed value. Later posts will talk about this in great detail.
Immutability goes hand-in-hand with pure functions. Since pure functions aren’t allowed to have side-effects, they aren’t allowed to change outside data structures. They are forced to work with data in an immutable way.
Where to Start?
The easiest way to start thinking functionally is to start replacing loops with collection-iteration functions.
If you come from another language that has these functions (Ruby and Smalltalk are but two examples), you might already be familiar with these.
Note that these functions are all (with the exception of
reject) available on
Array.prototype, so you don’t need Ramda to start using them. However, I’ll use the Ramda versions for consistency with the rest of the series.
Rather than writing an explicit loop, try using the
forEach function instead. That is:
forEach takes a function and an array, and calls the function on each element of the array.
forEach is the most approachable of these functions, it is used the least of any of them when doing functional programming. It doesn’t return a value, so is really only used for calling functions that have side-effects.
The next most important function to learn is
map applies a function to each element of an array. However, unlike
forEach, map collects the results of applying the function into a new array and returns it.
Here’s an example:
This is using an anonymous function, but we can just as easily use a named function here:
Next up, let’s look at
reject. As its name might suggest,
filter selects elements from an array based on some function. For example:
filter applies its function (
isEven in this case) to each element of the array. Whenever the function returns a “truthy” value, the corresponding element is included in the result. Whenever the function returns a “falsy” value, the corresponding element is excluded (filtered out) from the array.
reject does exactly the same thing, but in reverse. It keeps the elements for which the function returns a falsy value and excludes the values for which it returns a truthy value.
find applies a function to each element of an array and returns the first element for which the function returns a truthy value.
reduce is a little bit more complicated than the other functions we’ve seen so far. It is worth knowing, but if you have trouble understanding it at first, don’t let that stop you. You can get a long way without understanding it.
reduce takes a two-argument function, and initial value, and the array to operate on.
The first argument to the function we pass in is called the “accumulator” and the second argument is the value from the array. The function needs to return a new accumulator value.
Let’s look at an example and then walk through what’s going on.
reducefirst calls the function (
add) with the initial value (
5) and the first element of the array (
addreturns a new accumulator value (
5 + 1 = 6).
addagain, this time with the new accumulator value (
6) and the next value from the array (
8and the next value (
3), resulting in
addone last time with
11and the last value of the array (
4), resulting in
reducereturns the final accumulated value as its result (
By starting with these collection-iteration functions, you can get used to the idea of passing functions to other functions. You might have used these in other languages without realizing you were doing some functional programming.
The next post in this series, Combining Functions, shows how we can take the next step and start combining functions in new and interesting ways.