Working with functions

 

Sometimes, the elegant implementation is a function. Not a method. Not a class. Not a framework. Just a function.

 
 --John Carmack, lead programmer of the Doom video game

Functional programming is all about decomposing a problem into a set of functions. Often, functions are chained together, nested within each other, passed around, and treated as first-class citizens. If you've used frameworks such as jQuery and Node.js, you've probably used some of these techniques, you just didn't realize it!

Let's start with a little JavaScript dilemma.

Say we need to compile a list of values that are assigned to generic objects. The objects could be anything: dates, HTML objects, and so on.

var
  obj1 = {value: 1},
  obj2 = {value: 2},
  obj3 = {value: 3};

var values = [];
function accumulate(obj) {
  values.push(obj.value);
}
accumulate(obj1);
accumulate(obj2);
console.log(values); // Output: [obj1.value, obj2.value]

It works but it's volatile. Any code can modify the values object without calling the accumulate() function. And if we forget to assign the empty set, [], to the values instance then the code will not work at all.

But if the variable is declared inside the function, it can't be mutated by any rogue lines of code.

function accumulate2(obj) {
  var values = [];
  values.push(obj.value);
  return values;
}
console.log(accumulate2(obj1)); // Returns: [obj1.value]
console.log(accumulate2(obj2)); // Returns: [obj2.value]
console.log(accumulate2(obj3)); // Returns: [obj3.value]

It does not work! Only the value of the object last passed in is returned.

We could possibly solve this with a nested function inside the first function.

var ValueAccumulator = function(obj) {
  var values = []
  var accumulate = function() {
    values.push(obj.value);   
  };
  accumulate();
  return values;
};

But it's the same issue, and now we cannot reach the accumulate function or the values variable.

What we need is a self-invoking function.

Self-invoking functions and closures

What if we could return a function expression that in-turn returns the values array? Variables declared in a function are available to any code within the function, including self-invoking functions.

By using a self-invoking function, our dilemma is solved.

var ValueAccumulator = function() {
  var values = [];
  var accumulate = function(obj) {
    if (obj) {
      values.push(obj.value);
      return values;
    }
    else {
      return values;
    }
  };
  return accumulate;
};

//This allows us to do this:
var accumulator = ValueAccumulator();
accumulator(obj1); 
accumulator(obj2); 
console.log(accumulator()); 
// Output: [obj1.value, obj2.value]

It's all about variable scoping. The values variable is available to the inner accumulate() function, even when code outside the scope calls the functions. This is called a closure.

Note

Closures in JavaScript are functions that have access to the parent scope, even when the parent function has closed.

Closures are a feature of all functional languages. Traditional imperative languages do not allow them.

Higher-order functions

Self-invoking functions are actually a form of higher-order functions. Higher-order functions are functions that either take another function as the input or return a function as the output.

Higher-order functions are not common in traditional programming. While an imperative programmer might use a loop to iterate an array, a functional programmer would take another approach entirely. By using a higher-order function, the array can be worked on by applying that function to each item in the array to create a new array.

This is the central idea of the functional programming paradigm. What higher-order functions allow is the ability to pass logic to other functions, just like objects.

Functions are treated as first-class citizens in JavaScript, a distinction JavaScript shares with Scheme, Haskell, and the other classic functional languages. This may sound bizarre, but all this really means is that functions are treated as primitives, just like numbers and objects. If numbers and objects can be passed around, so can functions.

To see this in action, let's use a higher-order function with our ValueAccumulator() function from the previous section:

// using forEach() to iterate through an array and call a 
// callback function, accumulator, for each item
var accumulator2 = ValueAccumulator();
var objects = [obj1, obj2, obj3]; // could be huge array of objects
objects.forEach(accumulator2);
console.log(accumulator2());

Pure functions

Pure functions return a value computed using only the inputs passed to it. Outside variables and global states may not be used and there may be no side effects. In other words, it must not mutate the variables passed to it for input. Therefore, pure functions are only used for their returned value.

A simple example of this is a math function. The Math.sqrt(4) function will always return 2, does not use any hidden information such as settings or state, and will never inflict any side effects.

Pure functions are the true interpretation of the mathematical term for 'function', a relation between inputs and an output. They are simple to think about and are readily re-usable. Because they are totally independent, pure functions are more capable of being used again and again.

To illustrate this, compare the following non-pure function to the pure one.

// function that prints a message to the center of the screen
var printCenter = function(str) {
  var elem = document.createElement("div");
  elem.textContent = str;
  elem.style.position = 'absolute';
  elem.style.top = window.innerHeight/2+"px";
  elem.style.left = window.innerWidth/2+"px";
  document.body.appendChild(elem);
};
printCenter('hello world'),
// pure function that accomplishes the same thing
var printSomewhere = function(str, height, width) {
  var elem = document.createElement("div");
  elem.textContent = str;
  elem.style.position = 'absolute';
  elem.style.top = height;
  elem.style.left = width;
  return elem;
};
document.body.appendChild(printSomewhere('hello world', window.innerHeight/2)+10+"px",window.innerWidth/2)+10+"px")
);

While the non-pure function relies on the state of the window object to compute the height and width, the pure, self-sufficient function instead asks that those values be passed in. What this actually does is allow the message to be printed anywhere, and this makes the function much more versatile.

And while the non-pure function may seem like the easier option because it performs the appending itself instead of returning an element, the pure function printSomewhere() and its returned value play better with other functional programming design techniques.

var messages = ['Hi', 'Hello', 'Sup', 'Hey', 'Hola'];
messages.map(function(s,i){
  return printSomewhere(s, 100*i*10, 100*i*10);
}).forEach(function(element) {
  document.body.appendChild(element);
});

Note

When the functions are pure and don't rely on state or environment, then we don't care about when or where they actually get computed. We'll see this later with lazy evaluation.

Anonymous functions

Another benefit of treating functions as first-class objects is the advent of anonymous functions.

As the name might imply, anonymous functions are functions without names. But they are more than that. What they allow is the ability to define ad-hoc logic, on-the-spot and as needed. Usually, it's for the benefit of convenience; if the function is only referred to once, then a variable name doesn't need to be wasted on it.

Some examples of anonymous functions are as follows:

// The standard way to write anonymous functions
function(){return "hello world"};

// Anonymous function assigned to variable
var anon = function(x,y){return x+y};

// Anonymous function used in place of a named callback function, 
// this is one of the more common uses of anonymous functions.
setInterval(function(){console.log(new Date().getTime())}, 1000);
// Output:  1413249010672, 1413249010673, 1413249010674, ...

// Without wrapping it in an anonymous function, it immediately // execute once and then return undefined as the callback:
setInterval(console.log(new Date().getTime()), 1000)
// Output:  1413249010671

A more involved example of anonymous functions used within higher-order functions:

function powersOf(x) {
  return function(y) {
    // this is an anonymous function!
    return Math.pow(x,y);
  };
}
powerOfTwo = powersOf(2);
console.log(powerOfTwo(1)); // 2
console.log(powerOfTwo(2)); // 4
console.log(powerOfTwo(3)); // 8

powerOfThree = powersOf(3);
console.log(powerOfThree(3));  // 9
console.log(powerOfThree(10)); // 59049

The function that is returned doesn't need to be named; it can't be used anywhere outside the powersOf() function, and so it is an anonymous function.

Remember our accumulator function? It can be re-written using anonymous functions.

var
  obj1 = {value: 1},
  obj2 = {value: 2},
  obj3 = {value: 3};

var values = (function() {
  // anonymous function
  var values = [];
  return function(obj) {
    // another anonymous function!
    if (obj) {
      values.push(obj.value);
      return values;
    }
    else {
      return values;
    }
  }
})(); // make it self-executing
console.log(values(obj1)); // Returns: [obj.value]
console.log(values(obj2)); // Returns: [obj.value, obj2.value]

Right on! A pure, high-order, anonymous function. How did we ever get so lucky? Actually, it's more than that. It's also self-executing as indicated by the structure, (function(){...})();. The pair of parentheses following the anonymous function causes the function to be called right away. In the above example, the values instance is assigned to the output of the self-executing function call.

Note

Anonymous functions are more than just syntactical sugar. They are the embodiment of Lambda calculus. Stay with me on this… Lambda calculus was invented long before computers or computer languages. It was just a mathematical notion for reasoning about functions. Remarkably, it was discovered that—despite the fact that it only defines three kinds of expressions: variable references, function calls, and anonymous functions—it was Turing-complete. Today, Lambda calculus lies at the core of all functional languages if you know how to find it, including JavaScript.

For this reason, anonymous functions are often called lambda expressions.

One drawback to anonymous functions remains. They're difficult to identify in call stacks, which makes debugging trickier. They should be used sparingly.

Method chains

Chaining methods together in JavaScript is quit common. If you've used jQuery, you've likely performed this technique. It's sometimes called the "Builder Pattern".

It's a technique that is used to simplify code where multiple functions are applied to an object one after another.

// Instead of applying the functions one per line...
arr = [1,2,3,4];
arr1 = arr.reverse();
arr2 = arr1.concat([5,6]);
arr3 = arr2.map(Math.sqrt);
// ...they can be chained together into a one-liner
console.log([1,2,3,4].reverse().concat([5,6]).map(Math.sqrt));
// parentheses may be used to illustrate
console.log(((([1,2,3,4]).reverse()).concat([5,6])).map(Math.sqrt) );

This only works when the functions are methods of the object being worked on. If you created your own function that, for example, takes two arrays and returns an array with the two arrays zipped together, you must declare it as a member of the Array.prototype object. Take a look at the following code snippet:

Array.prototype.zip = function(arr2) {
  // ...
}

This would allow us to the following:

arr.zip([11,12,13,14).map(function(n){return n*2});
// Output: 2, 22, 4, 24, 6, 26, 8, 28

Recursion

Recursion is likely the most famous functional programming technique. If you don't know by now, a recursive function is a function that calls itself.

When a functions calls itself, something strange happens. It acts both as a loop, in that it executes the same code multiple times, and as a function stack.

Recursive functions must be very careful to avoid an infinite loop (rather, infinite recursion in this case). So just like loops, a condition must be used to know when to stop. This is called the base case.

An example is as follows:

var foo = function(n) {
  if (n < 0) {
    // base case
    return 'hello';
  }
  else {
    // recursive case
    foo(n-1);
  }
}
console.log(foo(5));

It's possible to convert any loop to a recursive algorithm and any recursive algorithm to a loop. But recursive algorithms are more appropriate, almost necessary, for situations that differ greatly from those where loops are appropriate.

A good example is tree traversal. While it's not too hard to traverse a tree using a recursive function, a loop would be much more complex and would need to maintain a stack. And that would go against the spirit of functional programming.

var getLeafs = function(node) {
  if (node.childNodes.length == 0) {
    // base case
    return node.innerText;
  }
  else {
    // recursive case: 
    return node.childNodes.map(getLeafs);
  }
}

Divide and conquer

Recursion is more than an interesting way to iterate without for and while loops. An algorithm design, known as divide and conquer, recursively breaks problems down into smaller instances of the same problem until they're small enough to solve.

The historical example of this is the Euclidan algorithm for finding the greatest common denominator for two numbers.

function gcd(a, b) {
  if (b == 0) {
    // base case (conquer)
    return a;
  }
  else {
    // recursive case (divide)
    return gcd(b, a % b);
  }
}

console.log(gcd(12,8));
console.log(gcd(100,20));

So in theory, divide and conquer works quite eloquently, but does it have any use in the real world? Yes! The JavaScript function for sorting arrays is not very good. Not only does it sort the array in place, which means that the data is not immutable, but it is unreliable and inflexible. With divide and conquer, we can do better.

The merge sort algorithm uses the divide and conquer recursive algorithm design to efficiently sort an array by recursively dividing the array into smaller sub-arrays and then merging them together.

The full implementation in JavaScript is about 40 lines of code. However, pseudo-code is as follows:

var mergeSort = function(arr){
  if (arr.length < 2) {
    // base case: 0 or 1 item arrays don't need sorting
    return items;
  }
  else {
    // recursive case: divide the array, sort, then merge
    var middle = Math.floor(arr.length / 2);
    // divide
    var left = mergeSort(arr.slice(0, middle));
    var right = mergeSort(arr.slice(middle));
    // conquer
    // merge is a helper function that returns a new array
    // of the two arrays merged together
    return merge(left, right);
  }
}

Lazy evaluation

Lazy evaluation, also known as non-strict evaluation, call-by-need and deffered execution, is an evaluation strategy that waits until the value is needed to compute the result of a function and is particularly useful for functional programming. It's clear that a line of code that states x = func() is calling for x to be assigned to the returned value by func(). But what x actually equates to does not matter until it is needed. Waiting to call func() until x is needed is known as lazy evaluation.

This strategy can result in a major increase in performance, especially when used with method chains and arrays, the favorite program flow techniques of the functional programmer.

One exciting benefit of lazy evaluation is the existence of infinite series. Because nothing is actually computed until it can't be delayed any further, it's possible to do this:

// wishful JavaScript pseudocode:
var infinateNums = range(1 to infinity);
var tenPrimes = infinateNums.getPrimeNumbers().first(10);

This opens the door for many possibilities: asynchronous execution, parallelization, and composition, just to name a few.

However, there's one problem: JavaScript does not perform Lazy evaluation on its own. That being said, there exist libraries for JavaScript that simulate lazy evaluation very well. That is the subject of Chapter 3, Setting Up the Functional Programming Environment.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.4.174