Lenses and Partial-application
Tracy Gilmore
Posted on March 19, 2022
There is a concept from the Functional Programming camp known as lenses that can simplify accessing values (properties) in data structures (objects and arrays). Whilst not directly supported in JavaScript it can be implemented easily in a variety of ways and is an effect technique well worth learning.
To demonstrate the concept we will use another FP technique called partial-application to simplify the task of sorting arrays of objects. We will look at three methods of implementing partial-application. The first mechanism makes use of JS's bind
method, second approach uses a closure and recursion. The third utilises the (dreaded) array reduce
method.
A short explanation of Partial-Application
In brief the technique of partial-application enables the programmer to supply arguments in stages and execute the function only once all of the required arguments have been supplied. This is in contrast to the more conventional approach of supplying all the arguments at the same time and executing the function immediately.
Benefits of this technique
One of the benefits of this technique is that those parameters that do not change between calls can be supplied once whilst those than change on each call can be provided last minute.
Another, and probably more useful, benefit of this technique is we can effectively define two (or more) interfaces for the function. For example. An array's map
method expects a transform function with the interface of (item, index?, array?)
where item is each entry in the array, index (optional) is the subscript of the item in the array and array (again optional) is the array itself. We cannot supply additional parameters directly which can limit reuse of the function. Using partial-application we can create the transform function with the expected interface using another function that is supplied with additional arguments, which are in scope (and accessible) within the transform function.
Please add a comment below if you would like me to demonstrate this feature in another post but now back to the original topic.
A refresher on sorting an array
The Array
object has a method called sort
that anticipates a comparison function used to arrange items in the array (see MDN for more details on sort
). The function is called several times during the sort
operation, requires two parameters and returns a numeric value according to the following rules:
- zero indicates the values are the same
- positive values indicate the items are in descending order
- negative values indicate the items are in ascending order
Let's check out a simple example using a list of names (strings).
const testData = ['Bob', 'Chris', 'Eve', 'Alice', 'Dave'];
testData.sort((person1, person2) => {
if (person1 === person2) return 0;
if (person1 > person2) return 1;
return -1;
});
console.table(testData);
/* OUTPUT
┌─────────┬─────────┐
│ (index) │ Values │
├─────────┼─────────┤
│ 0 │ 'Alice' │
│ 1 │ 'Bob' │
│ 2 │ 'Chris' │
│ 3 │ 'Dave' │
│ 4 │ 'Eve' │
└─────────┴─────────┘
*/
Now we will 'up the ante' by sorting an array of objects by a slightly nested property.
const testData = [
{ name: 'Chris', dob: { year: 1980, month: 2, day: 1 } },
{ name: 'Bob', dob: { year: 1980, month: 8, day: 5 } },
{ name: 'Eve', dob: { year: 1980, month: 4, day: 2 } },
{ name: 'Dave', dob: { year: 1980, month: 6, day: 4 } },
{ name: 'Alice', dob: { year: 1980, month: 4, day: 3 } },
];
testData.sort((person1, person2) =>
if (person1.dob.month === person2.dob.month) return 0;
if (person1.dob.month > person2.dob.month) return 1;
return -1;
);
console.table(
testData.map(person => ({
name: person.name,
month: person.dob.month,
day: person.dob.day,
}))
);
/* OUTPUT
┌─────────┬─────────┬───────┬─────┐
│ (index) │ name │ month │ day │
├─────────┼─────────┼───────┼─────┤
│ 0 │ 'Chris' │ 2 │ 1 │
│ 1 │ 'Eve' │ 4 │ 2 │
│ 2 │ 'Alice' │ 4 │ 3 │
│ 3 │ 'Dave' │ 6 │ 4 │
│ 4 │ 'Bob' │ 8 │ 5 │
└─────────┴─────────┴───────┴─────┘
*/
Even with this relatively simple example the comparison function is starting to get a bit messy and repetitious (person_.dob.month). We can simplify it using a technique inspired by Functional Programming's lenses to access object properties.
I this first attempt we create a function that requires one of the items from the array and returns the value of the property we want to sort by. In this example the syntax for the sort
comparison is slightly different but the effect is the same. See my note on this aspect towards the end of this post to find out more.
function lookup(person) {
return person['dob']['month'];
}
testData.sort(
(person1, person2) =>
-(lookup(person1) < lookup(person2)) ||
+(lookup(person1) > lookup(person2))
);
Using the JS bind
method
The above comparison function is cleaner and more dynamic but the lookup
function just moves referencing of the property out of the comparison function and remains very specific. We can do better by creating a lens (aka lookupGenerator
in the following examples) using partial-application.
In the following example we will use the JS OO facility bind
to apply, partially, lookupGenerator
to create the lookup
function.
function lookupGenerator(prop1, prop2, obj) {
return obj[prop1][prop2];
}
const lookup = lookupGenerator.bind(null, 'dob', 'month');
When the lookupGenerator
function is called it is supplied with arguments to populate the first two properties prop1
and prop2
but not the third. Using the bind
method returns a new function that is assigned to lookup
. The new function only requires the third parameter to be supplied in order for the lens to operate.
The sort
operation does not change, supplying the lens with the specific items out of the array that require comparing. Not how we satisfied the parameters (partially-applied the arguments) of the lens in two stages with the second being within the sort comparison function.
Using JS closure and recursion
The lookupGenerator
is still rather specific so here is another way of implementing a lens through partial-application using a closure, recursion along with rest and spread operations. This approach is more complicated but is far more dynamic and reusable.
function lookupGenerator(...props) {
const _lookupGenerator = (obj, prop, ...props) =>
prop ? _lookupGenerator(obj[prop], ...props) : obj;
return obj => _lookupGenerator(obj, ...props);
}
const lookup = lookupGenerator('dob', 'month');
In the above implementation of the lookupGenerator
lens we start by providing all of the properties (in sequence) required to locate the property we want to sort by but this time there can be any number of arguments and they are defined by the use case not implementation. The recursive process keeps calling _lookupGenerator
until all the supplied parameters are exhausted before returning a function to accept the final argument (the object) and execute the function to retrieve the value of the property within it.
Using the Array reduce
method
The third and final approach might be shorter but the fact it uses the Array reduce
method can make it appear more complicated. However, all that is happening here is the array on which the reduce
is being performed is the list of properties for the object lens.
The starting value of the accumulator is the object in focus. It still employs partial-application because the list of properties is passed on the first call, a function is returned. When the generated function is called it is passed the subject object and (if found) returns the value of the property.
function lookupGenerator(...props) {
return obj =>
props.reduce((o, p) =>
p in o ? o[p] : null, obj);
}
The last two examples (above) have the advantage that the generator can be reused and supplied with a variety of arguments. For instance we can even reference array subscripts as follows.
const dayOfFourth = lookupGenerator('3', 'dob', 'day');
console.log(dayOfFourth(testData));
// Fourth entry is 'Dave' with a day of birth of '4'
Conclusion
While this sort
example is rather simplistic I think it demonstrates adequately how lenses can simplify code such as the comparison function but providing a mechanism for locating properties deeply nested in objects and arrays.
Using the bind
method to create the lens demonstrates the concept but is rather limiting and specific (not reusable.) The recursive and reduce approaches might be more difficult to understand but are far more reusable.
The code illustrated in this post is not recommended for use in production but the concepts most certainly are. Libraries like lodash and underscope provide many tried and tested, production-ready functions, some from the FP camp, that can simplify your code and make it easier to create reusable code.
Finally, a note on the comparison function (I did promise)
Whilst writing this post I found I could write the comparison function as follows.
(person1, person2) =>
-(person1 < person2) || +(person1 > person2)
This is a technique I have not seen anywhere else and not used before myself, so I conducted some additional testing and found it worked. However, I am sure there are undiscovered pros and cons. There is a mix here of Boolean logic, (lazy) numeric evaluation and type coercion that TypeScript might object to but is sound JS.
How it works
The Boolean values true
and false
coerce to numeric values 1 and 0 respectively, so the numeric comparisons (less than and greater than) will first return a Boolean value before being converted to numeric values -1|0 and +1|0 respectively.
The logical or
(||
) performs lazy evaluation so if the two values being compared are in (less than) order the second (greater than) expression will not be performed and -1 will be return immediately. If the values being compared are equal both sides will result in 0 and zero will be returned (not false as might be suspected).
Supporting code for this post can be found at JSFiddle including some proof tests for my comparison function.
Supplemental
There is a supplementary post to this to describe an enhancement to the code.
Posted on March 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.