Filters and pipes

If you're at all familiar with the Unix command line or, to a lesser extent, the Windows command line, then you'll have probably made use of pipes. A pipe, which is represented by the | character is shorthand for "take the output of program A and put it into program B". This relatively simple idea makes the Unix command line incredibly powerful. For instance, if you wanted to list all the files in a directory and then sort them and filter for any which start with either the letters b or g and end with an f then the command might look like the following:

ls|sort|grep "^[gb].*f$"

The ls command lists all files and directories, the sort command sorts them, and the grep command matches file names against a regular expression. Running this command in the etc directory on an Ubuntu box in /etc would give a result which looks something like the following:

stimms@ubuntu1:/etc$ ls|sort|grep "^[gb].*f$"
blkid.conf
bogofilter.cf
brltty.conf
gai.conf
gconf
groff
gssapi_mech.conf

Some functional programming languages such as F# offer a special syntax for piping between functions. In F#, filtering a list for even numbers can be done in the following way:

[1..10] |>List.filter (fun n -> n% 2 = 0);;

This syntax is very nice-looking, especially when used for long chains of functions. As an example, taking a number, casting it to a float, square rooting it, and then rounding it would look like the following:

10.5 |> float |>Math.Sqrt |>Math.Round

This is a clearer syntax than the C-style syntax that would look more like the following:

Math.Round(Math.Sqrt((float)10.5))

Unfortunately, there is no ability to write pipes in JavaScript using a nifty F# style syntax, but we can still improve upon the normal method shown in the preceding code by using method chaining.

Everything in JavaScript is an object, which means that we can have some real fun adding functionality to existing objects to improve their look. Operating on collections of objects is a space in which functional programming provides some powerful features. Let's start by adding a simple filtering method to the array object. You can think of these queries as being like SQL database queries written in a functional fashion.

Implementation

We would like to provide a function that performs a match against each member of the array and returns a set of results:

Array.prototype.where = function (inclusionTest) {
  let results = [];
  for (let i = 0; i<this.length; i++) {
    if (inclusionTest(this[i]))
      results.push(this[i]);
  }
  return results;
};

The rather simple looking function allows us to quickly filter an array:

var items = [1,2,3,4,5,6,7,8,9,10];
items.where(function(thing){ return thing % 2 ==0;});

What we return is also an object, an array object in this case. We can continue to chain methods onto it like the following:

items.where(function(thing){ return thing % 2 ==0;})
  .where(function(thing){ return thing % 3 == 0;});

The result of this is an array containing only the number 6, as it is the only number between 1 and 10 which is both even and divisible by three. This method of returning a modified version of the original object without changing the original is known as a fluent interface. By not changing the original item array, we've introduced a small degree of immutability into our variables.

If we add another function to our library of array extensions, we can start to see how useful these pipes can be:

Array.prototype.select=function(projection){
  let results = [];
  for(let i = 0; i<this.length;i++){
    results.push(projection(this[i]));
  }
  return results;
};

This extension allows for projections of the original items based on an arbitrary projection function. Given a set of objects which contain IDs and names, we can use our fluent extensions to array to perform complex operations:

let children = [{ id: 1, Name: "Rob" },
{ id: 2, Name: "Sansa" },
{ id: 3, Name: "Arya" },
{ id: 4, Name: "Brandon" },
{ id: 5, Name: "Rickon" }];
let filteredChildren = children.where(function (x) {
  return x.id % 2 == 0;
}).select(function (x) {
  return x.Name;
});

This code will build a new array which contains only children with even IDs and instead of full objects, the array will contain only their names: Sansa and Brandon. For those familiar with .Net these functions may look very familiar. The Language Integrated Queries (LINQ) library on .Net provides similarly named functional inspired functions for the manipulation of collections.

Chaining functions in this manner can be both easier to understand and easier to build than alternatives: temporary variables are avoided and the code made terser. Consider the preceding example re-implemented using loops and temporary variables:

let children = [{ id: 1, Name: "Rob" },
{ id: 2, Name: "Sansa" },
{ id: 3, Name: "Arya" },
{ id: 4, Name: "Brandon" },
{ id: 5, Name: "Rickon" }];
let evenIds = [];
for(let i=0; i<children.length;i++)
{
  if(children[i].id%2==0)
    evenIds.push(children[i]);
}
let names = [];
for(let i=0; i< evenIds.length;i++)
{
  names.push(evenIds[i].name);
}

A number of JavaScript libraries such as d3 are constructed to encourage this sort of programming. At first it seems like the code created following this convention is bad due to very long line length. I would argue that this is a function of line length not being a very good tool to measure complexity rather than an actual problem with the approach.

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

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