{ Introduction to Functional Programming. }

Objectives:

By the end of this chapter, you should be able to:

  • Define what functional programming is
  • Understand key components of functional programming like pure functions, composition and currying
  • Write curry, compose, and sequence functions

Functional Programming

When first learning about functional programming, you're likely 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. We'll discuss a few of these conditions in this section.

Pure Functions

First, we want to always strive to create "pure" functions. 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;
}

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!

function addNameToObject(obj,val){
    var newObj = {name: val};
    return Object.assign({}, obj, newObj);
}

The function is pure because there is a not side effect and we are not mutating or changing the start variable. If we call this function again, we will not mutate any existing variables!

Here's another one:

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]
arr; // [1, 2, 3, 4]

The function is pure because there is a not side effect and we are notmutating or changing the arr variable. if we call this function again, we will get a different value!

Let's do one more:

var startCountingFrom = (function(num) {

    var count = 0;

    return function(num) {
        count++;
        return count + num;
    }

})();

This may be a little tricky to read, but if you try to use this function you should have an easier time discovering what's going on:

startCountingFrom(0); // 0
startCountingFrom(0); // 1
startCountingFrom(0); // 2

This function is also impure! Even though it's not mutating any variables in the global scope, it doesn't provide the same output given the same input. This function has the potentially to be especially hard to reason about, because you can't reliabily predict what value it will return if you see it being used somewhere in your code.

It's worth trying to understand how this function is working. (Hint: think about closure!)

You can read more about pure functions here, here, and if you are looking for a more advanced read, take a look here.

Closures

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 
        // and which has returned by the time inner makes use of it
        return num + newNum;
    }
}

outer()(5); // 15
outer()(10); // 20
outer(10)(); // NaN - why is this?

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

Currying is the process of breaking down a function that takes multiple arguments into a series of functions that take some subset of the arguments. Let's examine a very simple curry function. We will partially apply a function's 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(add,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

If you are not sure what the ... is doing, you can read more about the rest operator here. Also, note that strings and arrays have a length property - but so do functions! A function's length can let us know how many arguments are in its definition!

You can read more about currying here, here, and here.

Composition

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
  • filter out everything that is not a vowel
  • join it back into a string with a ":" in between each vowel.

We could do that as:

function convert(str){
    return str.toUpperCase().split('').filter(function(val){
        return val.match(/[AEIOU]/);
    }).join(":")
}

convert("hello") // "O:E"

This works totally fine, but this function is doing a lot of things. What if we could acheive the same functionality by combining several functions, each of which only does one thing? This is the idea behind composition.

You may have learned about composition in math class: if you have one function f, and another function g, you can compose the two functions and apply them to an input x. The mathematical notation looks like this: f(g(x)); in other words, we start with the input x, then apply g to that input, then apply f to the output of g. In other words, when we think about composition we start from the inside out.

Here is what it might look like in JavaScript; let's call our function compose. You can see an implementation of an ES5 compose here, but let's use ES2015!

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 isVowel = complexCurry(function(char){
    return char.match(/[AEIOU]/);
})

var split = complexCurry(function(delimiter, str){
    return str.split(delimiter);
})

var toUpperCase = complexCurry(function(str){
    return str.toUpperCase();
})

var convertLetters = compose(join(':'), filter(isVowel), split(''),toUpperCase());

console.log(convertLetters('This is some pretty crazy stuff')); // I:I:O:E:E:A:U

If we wanted to start with the outer most function f -> g -> x we can use what is called a sequence function. This is also known as pipe or flow in libraries like Ramda.js and Lodash. The only difference compared to using compose is that we're calling reduce rather than reduceRight:

function sequence(...functions) {
    return function(start){
        return functions.reduce(function(acc, next) {
            return next(acc);
        } , start);
    } 
} 

We could then do something like this in what might be a more understandable order:

var convertLetters = sequence(toUpperCase(), split(''), filter(isVowel), join(':'));

console.log(convertLetters('This is some pretty crazy stuff')); // I:I:O:E:E:A:U

When you're ready, move on to Introduction to Design Patterns

Continue

Creative Commons License