Information


Blog Posts


Collections



Contact


Things Ian Says

Functional Example

This blog entry will take the techniques discussed in the previous articles about functional Javascript and currying and apply them to an example problem.

Suppose we ran an online bookstore, selling both paper and electronic books. We may use a data structure like this to represent them:

// Expression
books

// Result
[ { id: 1,
    title: 'Eloquent Javascript',
    author: 'Marijn Haverbecke',
    price: { paper: 16.52, kindle: 15.69 } },
  { id: 2,
    title: 'Javascript Patterns',
    author: 'Stoyan Stefanov',
    price: { paper: 16.58, kindle: 11.51 } },
  { id: 3,
    title: 'Javascript the Good Parts',
    author: 'Douglas Crockford',
    price: { paper: 13.59, kindle: 12.91 } },
  { id: 4,
    title: 'AngularJS',
    author: 'Brad Green and Shyam Seshadri',
    price: { paper: 20.04, kindle: 16.21 } } ]

As you can see, this is an array of objects, where each object can itself contain further objects.

This may come out of a database like MongoDB, a document store like Solr or perhaps from some external service. We will then want to manipulate this structure, for example to display on a web page.

We will start by defining a get function to get an element from an object. This will just use standard Javascript syntax (value = object[key]), but we will build it into a curried function so it is easy to apply to the entire array:

// Find something by key from an object
function get (key) {

    return function (obj) {
        return obj[key];
    };
}

Let’s do a quick test to check that our function is behaving as we expect. Partial application returns a function:

// Expression
get("b")

// Result
[Function]

Full evaluation returns a result:

// Expression
get("b")({a: 1, b: 2, c: 3})

// Result
2

So, now we can use our get function. Suppose we want to get a list of all our book titles, we can just partially apply our get function with title as the parameter, then map that over our array of books:

// Expression
books.map(get("title"))

// Result
[ 'Eloquent Javascript',
  'Javascript Patterns',
  'Javascript the Good Parts',
  'AngularJS' ]

That works for any key, so we can also get a list of authors:

// Expression
books.map(get("author"))

// Result
[ 'Marijn Haverbecke',
  'Stoyan Stefanov',
  'Douglas Crockford',
  'Brad Green and Shyam Seshadri' ]

If we get a list of prices, then because of our data structure we end up with an array of objects — prices for both paper and kindle:

// Expression
books.map(get("price"))

// Result
[ { paper: 16.52, kindle: 15.69 },
  { paper: 16.58, kindle: 11.51 },
  { paper: 13.59, kindle: 12.91 },
  { paper: 20.04, kindle: 16.21 } ]

Because we have an array of objects, we can apply our get function again to drill down and get just the kindle prices:

// Expression
books.map(get("price")).map(get("kindle"))

// Result
[ 15.69, 11.51, 12.91, 16.21 ]

If we want to make our code more compact or more restrictive, we can store the partial evaluation as another function (like we did to create a double function from a multiplier function in our currying blog article).

So, we can create a getTitle or getPrice or getKindlePrice function very easily:

getTitle = get("title");
getPrice = get("price");
getKindlePrice = get("kindle");

We can then use this new function instead of the partial application:

// Expression
books.map(getTitle)

// Result
[ 'Eloquent Javascript',
  'Javascript Patterns',
  'Javascript the Good Parts',
  'AngularJS' ]

And we can chain together the functions just like we did earlier to drill down into nested objects:

// Expression
books.map(getPrice).map(getKindlePrice)

// Result
[ 15.69, 11.51, 12.91, 16.21 ]

This is looking good, but we’ve got no way of reducing this down further. It would be nice to move from getTitle to getTitles, but because our map function is an attribute of the array object, we can’t do this.

What we’ll do to get around this is create a curried map function:

// Create a map function
function map (fn, arr) {
    return function (arr) {
        return arr.map(fn);
    };
}

Let’s test that we can do partial application, and that the everything is still working:

// Expression
map(getTitle)

// Result
[Function]

// Expression
map(getTitle)(books)

// Result
[ 'Eloquent Javascript',
  'Javascript Patterns',
  'Javascript the Good Parts',
  'AngularJS' ]

Now we can define a getTitles function:

// Expression
getTitles = map(getTitle)

// Result
[Function]

And check that it’s working:

// Expression
getTitles(books)

// Result
[ 'Eloquent Javascript',
  'Javascript Patterns',
  'Javascript the Good Parts',
  'AngularJS' ]

Assuming that we have also defined getPrices and getKindlePrices functions, we can chain them together, just like we did earlier:

// Expression
getKindlePrices(getPrices(books))

// Result
[ 15.69, 11.51, 12.91, 16.21 ]

It’s worth pausing at this point to take a look back at what we’ve done. By defining two generic functions (get and map — both based on Javascript built-ins), and using partial application, we have been able to build up an extensive suite of data manipulation functions.

Let’s be a bit more ambitious and create some functions to allow us to query our data structure. We’ll start by creating a where function, which takes a key as a parameter and returns a new object where the key’s field is stored as fieldValue and the original object is preserved in data:

// Isolate a field's value for future operations
function where (key) {

    return function (obj) {

        // If we already have a field value, drill into it
        if (obj.fieldValue) {
            obj.fieldValue = obj.fieldValue[key];
            return obj;
        }

        // Otherwise, it's the same as before
        return {
            fieldValue: obj[key],
            data: obj
        };
    };
}

You can see how this works, in this example:

// Expression
books.map(where("price"))

// Result
[ { fieldValue: { paper: 16.52, kindle: 15.69 },
    data:
     { id: 1,
       title: 'Eloquent Javascript',
       author: 'Marijn Haverbecke',
       price: [Object] } },
  { fieldValue: { paper: 16.58, kindle: 11.51 },
    data:
     { id: 2,
       title: 'Javascript Patterns',
       author: 'Stoyan Stefanov',
       price: [Object] } },
  { fieldValue: { paper: 13.59, kindle: 12.91 },
    data:
     { id: 3,
       title: 'Javascript the Good Parts',
       author: 'Douglas Crockford',
       price: [Object] } },
  { fieldValue: { paper: 20.04, kindle: 16.21 },
    data:
     { id: 4,
       title: 'AngularJS',
       author: 'Brad Green and Shyam Seshadri',
       price: [Object] } } ]

We’ve written this function so that repeated application of the where function drills down into the fields. So we can apply it again to get the kindle price:

// Expression
books.map(where("price")).map(where("kindle"))

// Result
[ { fieldValue: 15.69,
    data:
     { id: 1,
       title: 'Eloquent Javascript',
       author: 'Marijn Haverbecke',
       price: [Object] } },
  { fieldValue: 11.51,
    data:
     { id: 2,
       title: 'Javascript Patterns',
       author: 'Stoyan Stefanov',
       price: [Object] } },
  { fieldValue: 12.91,
    data:
     { id: 3,
       title: 'Javascript the Good Parts',
       author: 'Douglas Crockford',
       price: [Object] } },
  { fieldValue: 16.21,
    data:
     { id: 4,
       title: 'AngularJS',
       author: 'Brad Green and Shyam Seshadri',
       price: [Object] } } ]

Now we have a way to isolate specific fields within our data structure, let’s create a function to do something with that. We’ll create an is function which operates on these results and can apply a function to test those results. An example of such a test would be a below function, which tests whether one value is below another. This is function will return either the embedded data object (i.e. the original structure) or null (depending on whether the test function returns true or false).

Note the separation of concerns in this approach — we have the is function dealing with the removal of elements and the test function which actually performs the test. We will pass the test function into the is function to achieve this:

// Run a function against an object's fieldValue
function is (fn) {

    return function (obj) {

        if (fn(obj.fieldValue)) {
            return obj.data;
        }

        return null;
    };
}

// Sample function, is x < y?
function below (y) {

    return function (x) {
        return x < y;
    };
}

// Check whether a value is non-null
function notNull (x) {
    return x !== null;
}

As you should expect by now, we are making extensive use of curried functions which will allow us to combine the functions in a flexible way. We have also defined a notNull function which we will use shortly.

So, now we can add our is function to our chain of function calls, passing in our below function to find some cheaper books:

// Expression
books
  .map(where("price"))
  .map(where("kindle"))
  .map(is(below(15)))

// Result
[ null,
  { id: 2,
    title: 'Javascript Patterns',
    author: 'Stoyan Stefanov',
    price: { paper: 16.58, kindle: 11.51 } },
  { id: 3,
    title: 'Javascript the Good Parts',
    author: 'Douglas Crockford',
    price: { paper: 13.59, kindle: 12.91 } },
  null ]

It’s worth having a look at the partial evaluations which are happening in that last part. We pass 15 in to the below function — this gives us a new function which will test whether its argument is below 15. We now pass this below 15 function into our is function – this gives us a function which tests whether the fieldValue in an object is below 15. Finally, we pass this function into the map function to apply it across an array. That is how we arrive at the result seen above.

We will now use the notNull function we defined earler, passing it into Javascript’s filter function to remove the null values from the previous step:

// Expression
books
  .map(where("price"))
  .map(where("kindle"))
  .map(is(below(15)))
  .filter(notNull)

// Result
[ { id: 2,
    title: 'Javascript Patterns',
    author: 'Stoyan Stefanov',
    price: { paper: 16.58, kindle: 11.51 } },
  { id: 3,
    title: 'Javascript the Good Parts',
    author: 'Douglas Crockford',
    price: { paper: 13.59, kindle: 12.91 } } ]

We can replace the native Javascript map and filter functions with our versions from earlier, to give us the following functional version:

// Expression
filter(notNull)(
   map(is(below(15)))(
      map(where("kindle"))(
         map(where("price"))(
            books))))

// Result
[ { id: 2,
    title: 'Javascript Patterns',
    author: 'Stoyan Stefanov',
    price: { paper: 16.58, kindle: 11.51 } },
  { id: 3,
    title: 'Javascript the Good Parts',
    author: 'Douglas Crockford',
    price: { paper: 13.59, kindle: 12.91 } } ]

It might not be obvious what the advantage of using these map and filter functions are over the native Javascript ones, but think of promises and there is a benefit. If you recall, the Promises/A+ standard defines a then function, which invokes a function taking one parameter and returns a new promise, allowing chains of then functions.

With curried functions, we have an ideal way of creating single parameter functions.

Suppose that instead of having our books structure available to us in a variable, we instead get it from making a REST call. We don’t want to tie up our execution waiting for the REST call to return, so we generate a promise instead and use that. So, suppose we have a makeRestCall function which returns a promise, which resolves to our books structure. We can then set up a series of then function calls using our functions:

// Expression
makeRestCall()
  .then(map(where("price")))
  .then(map(where("kindle")))
  .then(map(is(below(15))))
  .then(filter(notNull))
  .then(doSomethingUseful)

// Result
[ { id: 2,
    title: 'Javascript Patterns',
    author: 'Stoyan Stefanov',
    price: { paper: 16.58, kindle: 11.51 } },
  { id: 3,
    title: 'Javascript the Good Parts',
    author: 'Douglas Crockford',
    price: { paper: 13.59, kindle: 12.91 } } ]

That concludes this example, which I hope has given some idea about how functional techniques described in functional Javascript and currying fit well with Javascript, and enable easily understandable promise handlers to be created.