- Define what functional programming is
- Define what a pure function is
- Use built in functional methods forEach, filter, map, reduce
- Understand key components of functional programming like composition and currying
- Write curry, compose and flow functions
When first looking at what functional programming you come across a myriad of complex terms like monads and lambda calculus, but let's start with a simpler definition. Functional programming is an alternative to object oriented programming and it consists of building programs with small, reusable functions. That may seem trivial, but there are special conditions that these functions will need to have. First, we want to always strive to create "pure" functions.
In javascript, functions are first class citizens. This means that functions are just another data type in the language that we can use and pass around.
var sayMyName = function(name) {
console.log(name);
}
var toUpperCase = function(name) {
return name.toUpperCase();
}
function whatShouldIDoWithMyName(name, fn) {
return fn(name);
}
whatShouldIDoWithMyName("Tim", sayMyName);
whatShouldIDoWithMyName("Tim", toUpperCase);
A pure function is a predictable function that does not have an side-effects. What does that mean? When a pure function is called many times with the same input, it will always give the same output (this is also known as idempotence) and is predictable. Another characteristic of pure functions are that they do not modify external state, or change values outside of their scope.
Let's try to identify some pure and impure functions:
Are the following functions pure or impure?
var arr = [2,4,6];
function doubleValues(arr){
for(var i =0; i< arr.length; i++){
arr[i] = arr[i]*2;
}
}
doubleValues(arr);
arr; // [4, 8, 12]
doubleValues(arr);
arr; // [8, 16, 24]
The function is impure because there is a side effect, we are mutating or changing the arr
variable and if we call this function again, we will get a different value!
var arr = [2,4,6]
function doubleValues(arr){
return arr.map(function(val){
return val*2;
})
}
doubleValues(arr); // [4,8,12]
doubleValues(arr); // [4,8,12]
doubleValues(arr); // [4,8,12]
This function is pure because there is no side effect, if we wanted to double the result of double, we could combine these functions together! doubleValues(doubleValues(arr)) // [8,16,24]
and we still would not change the arr
variable. Pretty cool!
How about this one?
var start = {};
function addNameToObject(obj,val){
obj.name = val;
return obj;
}
addNametoObject(start, "Tim");
The function is impure because there is a side effect. We are mutating or changing the start
variable and if we call this function again, we will get a different value!
var start = {};
function addNameToObject(obj,val){
var newObj = {name: val};
return Object.assign({}, obj, newObj);
}
addNameToObject(start, "Tim");
The function is pure because there are no side effects and we are not mutating or changing the start
variable. if we call this function again, we will get the same result.
var arr = [1,2,3,4]
function addToArr(arr,val){
arr.push(val);
return arr;
}
addToArr(arr, 5); // [1,2,3,4,5]
arr; // [1,2,3,4,5]
The function is impure because there is a side effect and we are mutating or changing the arr
variable. if we call this function again, we will get a different value!
var arr = [1,2,3,4]
function addToArr(arr,val){
var newArr = arr.concat(val);
return newArr;
}
addToArr(arr, 5); // [1,2,3,4,5]
The function is pure because there are no side effects and we are not mutating or changing the arr
variable. If we call this function again, we will get the same value!
You can read more about pure functions here, here, and if you are looking for a more advanced read, take a look here
JavaScript has some built in iterators that are common in functional programming.
forEach:
var names = ['Tim', 'Matt', 'Elie'];
names.forEach(function(name) {
console.log(name);
});
map:
var movieData = {
"Search": [
{"Title":"The Shipping News","Year":"2001","imdbID":"tt0120824","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BNWEwMmNiNjctNDY5ZC00NDIyLWFiZDctNDdlZGU5NjA0MzA2L2ltYWdlXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"Broadcast News","Year":"1987","imdbID":"tt0092699","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BN2E1ZmU5NDQtNjdlZC00Y2VhLTk2MWMtMmU0M2YyY2E3MTkyXkEyXkFqcGdeQXVyMTAwMzUyOTc@._V1_SX300.jpg"},
{"Title":"Bad News Bears","Year":"2005","imdbID":"tt0408524","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMzMzZDU1MTEtMDMzYy00MDhkLTg5MGMtYjFkNzYyMTc3YmU0XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"The Bad News Bears","Year":"1976","imdbID":"tt0074174","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BNTU1ZjNmNjktNjk4Yy00MzdjLTk0YzktMDRlYTllMjE0N2I0XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"Breaking News","Year":"2004","imdbID":"tt0414931","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMjUyOTM2OTExOF5BMl5BanBnXkFtZTcwMDcxODIzMQ@@._V1_SX300.jpg"},{"Title":"The Bad News Bears in Breaking Training","Year":"1977","imdbID":"tt0075718","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTUwMjQwMDUxMV5BMl5BanBnXkFtZTcwNjkwOTIyMQ@@._V1_SX300.jpg"},
{"Title":"The Bad News Bears Go to Japan","Year":"1978","imdbID":"tt0077199","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQxNjI5MTM2N15BMl5BanBnXkFtZTcwMzQ0MzkyMQ@@._V1_SX300.jpg"},
{"Title":"Good News","Year":"1947","imdbID":"tt0039431","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYxMDU2NzMxMV5BMl5BanBnXkFtZTcwMjkxNzIyMQ@@._V1_SX300.jpg"},
{"Title":"News from Home","Year":"1977","imdbID":"tt0076452","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMjdkNmUzYzktYWU3OS00ZDA2LWI1ZjUtYjYxNGYyNDI0NWEzXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"News from a Personal War","Year":"1999","imdbID":"tt0296108","Type":"movie","Poster":"N/A"}
],
"totalResults":"1444",
"Response":"True"
}
console.log(movieData.Search.map(function(m) {
return m.Title;
}));
filter:
var movieData = {
"Search": [
{"Title":"The Shipping News","Year":"2001","imdbID":"tt0120824","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BNWEwMmNiNjctNDY5ZC00NDIyLWFiZDctNDdlZGU5NjA0MzA2L2ltYWdlXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"Broadcast News","Year":"1987","imdbID":"tt0092699","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BN2E1ZmU5NDQtNjdlZC00Y2VhLTk2MWMtMmU0M2YyY2E3MTkyXkEyXkFqcGdeQXVyMTAwMzUyOTc@._V1_SX300.jpg"},
{"Title":"Bad News Bears","Year":"2005","imdbID":"tt0408524","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMzMzZDU1MTEtMDMzYy00MDhkLTg5MGMtYjFkNzYyMTc3YmU0XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"The Bad News Bears","Year":"1976","imdbID":"tt0074174","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BNTU1ZjNmNjktNjk4Yy00MzdjLTk0YzktMDRlYTllMjE0N2I0XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"Breaking News","Year":"2004","imdbID":"tt0414931","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMjUyOTM2OTExOF5BMl5BanBnXkFtZTcwMDcxODIzMQ@@._V1_SX300.jpg"},{"Title":"The Bad News Bears in Breaking Training","Year":"1977","imdbID":"tt0075718","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTUwMjQwMDUxMV5BMl5BanBnXkFtZTcwNjkwOTIyMQ@@._V1_SX300.jpg"},
{"Title":"The Bad News Bears Go to Japan","Year":"1978","imdbID":"tt0077199","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQxNjI5MTM2N15BMl5BanBnXkFtZTcwMzQ0MzkyMQ@@._V1_SX300.jpg"},
{"Title":"Good News","Year":"1947","imdbID":"tt0039431","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYxMDU2NzMxMV5BMl5BanBnXkFtZTcwMjkxNzIyMQ@@._V1_SX300.jpg"},
{"Title":"News from Home","Year":"1977","imdbID":"tt0076452","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMjdkNmUzYzktYWU3OS00ZDA2LWI1ZjUtYjYxNGYyNDI0NWEzXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"News from a Personal War","Year":"1999","imdbID":"tt0296108","Type":"movie","Poster":"N/A"}
],
"totalResults":"1444",
"Response":"True"
}
console.log(movieData.Search.filter(function(m) {
return m.Title.includes("Bears");
}));
reduce:
Bad example:
var arr = [3,4,6,10];
console.log(arr.reduce(function(acc, n) {
return acc + n;
})); // prints 23
Somewhat better example:
var arr = [3,4,6,10];
console.log(arr.reduce(function(acc, n) {
return acc + n;
}, 50)); // prints 73
Even better example:
var string = "I'm a string with some vowels in it";
console.log(string.split("").reduce(function(acc, letter) {
if (acc[letter.toLowerCase()] !== undefined) {
acc[letter.toLowerCase()]++;
}
return acc;
}, {a: 0, e: 0, i: 0, o: 0, u: 0})); // output: {a: 1, e: 2, i: 5, o: 2, u: 0}
1. Use reduce to change the search results below into an object that maps imdbID to title:
var movieData = {
"Search": [
{"Title":"The Shipping News","Year":"2001","imdbID":"tt0120824","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BNWEwMmNiNjctNDY5ZC00NDIyLWFiZDctNDdlZGU5NjA0MzA2L2ltYWdlXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"Broadcast News","Year":"1987","imdbID":"tt0092699","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BN2E1ZmU5NDQtNjdlZC00Y2VhLTk2MWMtMmU0M2YyY2E3MTkyXkEyXkFqcGdeQXVyMTAwMzUyOTc@._V1_SX300.jpg"},
{"Title":"Bad News Bears","Year":"2005","imdbID":"tt0408524","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMzMzZDU1MTEtMDMzYy00MDhkLTg5MGMtYjFkNzYyMTc3YmU0XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"The Bad News Bears","Year":"1976","imdbID":"tt0074174","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BNTU1ZjNmNjktNjk4Yy00MzdjLTk0YzktMDRlYTllMjE0N2I0XkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"Breaking News","Year":"2004","imdbID":"tt0414931","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMjUyOTM2OTExOF5BMl5BanBnXkFtZTcwMDcxODIzMQ@@._V1_SX300.jpg"},{"Title":"The Bad News Bears in Breaking Training","Year":"1977","imdbID":"tt0075718","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTUwMjQwMDUxMV5BMl5BanBnXkFtZTcwNjkwOTIyMQ@@._V1_SX300.jpg"},
{"Title":"The Bad News Bears Go to Japan","Year":"1978","imdbID":"tt0077199","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQxNjI5MTM2N15BMl5BanBnXkFtZTcwMzQ0MzkyMQ@@._V1_SX300.jpg"},
{"Title":"Good News","Year":"1947","imdbID":"tt0039431","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYxMDU2NzMxMV5BMl5BanBnXkFtZTcwMjkxNzIyMQ@@._V1_SX300.jpg"},
{"Title":"News from Home","Year":"1977","imdbID":"tt0076452","Type":"movie","Poster":"https://images-na.ssl-images-amazon.com/images/M/MV5BMjdkNmUzYzktYWU3OS00ZDA2LWI1ZjUtYjYxNGYyNDI0NWEzXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"},
{"Title":"News from a Personal War","Year":"1999","imdbID":"tt0296108","Type":"movie","Poster":"N/A"}
],
"totalResults":"1444",
"Response":"True"
}
2. Implement a function called reject. The function should take in an array and a callback function. Reject will return a new array that contains all the values from the input array that have returned false after having the callback function applied to it.
examples:
reject([1,2,3,4], function(val){
return val > 2;
}); // [1,2]
reject([2,3,4,5], function(val){
return val % 2 === 0;
}); // [3,5]
Another very common tool with functional programming is the use of closures, or functions that make use variables defined in outer functions that have previously returned. We will see how closures allow us to make use of partial application, or partially calling and applying parameters to one function. We will also make heavy use of closures when we discuss currying. Here is an example of a simple closure
function outer(){
var num = 10;
return function inner(newNum){
// the inner function makes use of num
// which was defined in the outer function
// which has returned by the time inner makes use of it
return num + newNum;
}
}
While closures are a very powerful tool in functional programming (and in general), they can cause memory issues when not written properly. You can learn more about these issues (called memory leaks) here
Currying is the process of breaking down a function that takes multiple arguments into a series of functions that take part of the arguments. Let's examine a very simple curry function. We will partially apply a functions arguments one at a time
function simpleCurry(fn, ...outerArgs){
return function(...innerArgs){
return fn.apply(this, outerArgs.concat(innerArgs));
}
}
function add(a,b){
return a+b;
}
var a1 = simpleCurry(a1,2);
a1(10); // 12
This simpleCurry works fine when we only have two parameters, but what happens if we have an infinite number of arguments? Or what happens if we don't bother to pass in a value at all? Let's look at a more complex curry.
function complexCurry(fn) {
return function f1(...f1innerArgs) {
if (f1innerArgs.length >= fn.length) {
return fn.apply(this, f1innerArgs);
} else {
return function f2(...f2innerArgs) {
return f1.apply(this, f1innerArgs.concat(f2innerArgs));
}
}
};
}
complexCurry(add)()()()(2)()()()(4); // 6
You can read more about currying here, here, and here
Very commonly, currying is used to combine two or more functions to produce a new function. We call this process of combining functions "composition". Let's imagine that we want to do the following to a string of data
- Uppercase the string
- Reverse it
- filter out everything that is not a vowels
- join it back into a string with a ":" in the middle.
We could do that as:
function convert(str){
return str.toUpperCase()
.split('')
.reverse()
.filter(function(val){
return ["A","E","I","O","U"].indexOf(val) !== -1
})
.join(":")
}
convert("hello") // "O:E"
This works totally fine, but what if we could combine all our functions into a single function! When we think about composition we start from the inside out. So if our function is f(g(x))
- we would start with x, g and then f
. Here is what it might look like with a compose
function:
function compose(...functions) {
return function(start){
return functions.reduceRight(function(acc, next) {
return next(acc);
} , start);
}
}
Since we are composing functions from the inside out (f(g(x))
=> x -> g -> f
) we need to accumulate the functions from right to left, so we use reduceRight
. We will see there is a more readable way of doing this later on if we go from left to right.
Since we are passing function definitions we need to curry our functions in order to return one function at a time. Remember, the purpose of currying is to return a single argument back so let's bring that back:
function complexCurry(fn) {
return function f1(...f1innerArgs) {
if (f1innerArgs.length >= fn.length) {
return fn.apply(this, f1innerArgs);
} else {
return function f2(...f2innerArgs) {
return f1.apply(this, f1innerArgs.concat(f2innerArgs));
}
}
};
}
Now let's curry our functions and pass them into compose.
var join = complexCurry(function(str, arr){
return arr.join(str);
})
var filter = complexCurry(function(fn,arr){
return arr.filter(fn);
})
var removeLetters = complexCurry(function(str){
return ["A","E","I","O","U"].indexOf(str) !== -1;
})
var reverse = complexCurry(function(arr){
return arr.reverse();
})
var split = complexCurry(function(delimiter, str){
return str.split(delimiter);
})
var toUpperCase = complexCurry(function(str){
return str.toUpperCase();
})
var convertLetters = compose(
join(':'),
filter(removeLetters),
reverse(),
split(''),
toUpperCase()
);
console.log(convertLetters('This is some pretty crazy stuff')); //U:A:E:E:O:I:I
If we wanted to start with the outer most function f -> g -> x
we can use what is called a pipe
or flow
function.
function flow(...functions) {
return function(start){
return functions.reduce(function(acc, next) {
return next(acc)
} , start);
}
}
We could then hopefully do something like this:
var convertLetters = flow(
toUpperCase(),
split(''),
reverse(),
filter(removeLetters),
join(':')
);
console.log(convertLetters('This is some pretty crazy stuff')); //U:A:E:E:O:I:I
You can read more about function composition here