Higher order functions - step by step - part II
Receive functions as arguments
To swap the code that we execute on each
element of a collection we previously introduced the stream
objects. That way we had a common way to use the options we had at the moment. If we want to make it more generic, we need to remove more specific details from that. You can think that someone receives a text
and the outcome of that call is something that cannot be seen by the caller. That way we have removed the stream
and write
concepts. What we have left is just a function that receives a text
and does not return anything.
var each = function(collection, elementHandler) {
var i;
for (i = 0; i < collection.length; i++) {
elementHandler(collection[i]);
}
};
//in some part of the code:
each(eligibleDriverNames, consoleStream.write);
//in other part of the code:
each(eligibleDriverNames, fileStream.write);
Now, this each
function is very generic. It can be reused in a very wide context.
We can do the same with the remaining piece of code from the beginning:
var eligibleDriverNames = [];
for (var i = 0; i < people.length; i++) {
if (people[i].age >= minimumAgeToDrive) {
eligibleDriverNames.push(people[i].name);
}
}
To be able to do that we need to separate the parts involved in each responsibility. Let’s generalise the one that filters
the drivers first:
var eligibleDriverNames = [];
for (var i = 0; i < people.length; i++) {
if (people[i].age >= minimumAgeToDrive) {
eligibleDriverNames.push(people[i]);
}
}
var filter = function(collection, predicate) {
var filtered = [];
var i;
for (i = 0; i < collection.length; i++) {
if (predicate(collection[i])) {
filtered.push(collection[i]);
}
}
return filtered;
};
var canDrive = function(person){
return person.age >= minimumAgeToDrive;
};
var drivers = filter(people, canDrive);
And now the one that maps
each driver to his/her name:
var eligibleDriverNames = [];
for (var i = 0; i < drivers.length; i++) {
eligibleDriverNames.push(drivers[i].name);
}
var map = function(collection, transformation){
var mapped = [];
var i;
for (i = 0; i < collection.length; i++) {
mapped.push(transformation(collection[i]));
}
return mapped;
}
var getName = function(person){
return person.name;
};
var eligibleDriverNames = map(drivers, getName);
By hiding the for
loop inside these functions, we have created an abstraction. We have separated what to do from how to do it. Now the clients can depend only on what to do. That enables the possibility to change how to do it without affecting them.
For example: In the each
function what we do is: do something with each element in the collection. How we do it is: iterating sequentially with the for
loop.
We could change the implementation of each
for one that process the elements in parallel to take advantage of multi-core or several clusters. We could also change the implementation of map
for one that does the transformation only if the result is going to be used (lazy evaluation).