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.