DEV Community

Cover image for FP Lenses enhanced
Tracy Gilmore
Tracy Gilmore

Posted on • Updated on

FP Lenses enhanced

We ended my previous post on Lenses and Partial-application with the following implementation for the lens function lookupGenerator.

function lookupGenerator(...props) {
  return obj =>
    props
      .reduce((o, p) => 
        p in o ? o[p] : null, obj);
}
Enter fullscreen mode Exit fullscreen mode

Since the previous version I have revised it to include some enhancements.

Recap on what lenses are used for

A lens is a function used to extract a value from an object/array given the properties/subscripts that defines its path. For example:

const testObject = {
  alpha: [
    {beta: '42', gamma: [ 'A', 'B', 'C']},
    {beta: '666', gamma: [ 'a', 'b', 'c']}
  ]
};

const testLookup = lookupGenerator('alpha', 1, 'gamma', 2);

console.log(testLookup(testObject)); // 'c'
Enter fullscreen mode Exit fullscreen mode

Where lenses really come into their owns is when they are applied within a method on an array of objects, as follows:

const betaLookup = lookupGenerator('beta');

testObject.alpha.forEach(obj => console.log(betaLookup(obj)));

// Output: 42, 666
Enter fullscreen mode Exit fullscreen mode

The testObject in the above examples is quite small and simple but imagine using this technique to pass an array of more complicated objects through methods such as sort, map or filter.

So what is the limitation with the above implementation?

As discussed in my previous post, the above function employs partial-application to improve reuse. The function is called twice, once to provide the list of properties (and subscripts) used to navigate the object to find the required value. This returns a function that can be used several times by calling it with a compatible object (or array.)

There are a couple of ways to prescribe the route through the object to the required property. In the above example an array of property names and array subscripts were provided ('alpha', 1, 'gamma', 2) but another way is to provide the route as a string as follows 'alpha[1].gamma[2]'.

function lookupGenerator(...props) {
  return obj =>
    props
      .join('.')
      .split(/[\[\]\.]+/)
      .filter(item => item !== '')
      .reduce((o, p) =>
        typeof o === 'object' && o != null && 
        p in o ? o[p] : undefined, obj);
}
Enter fullscreen mode Exit fullscreen mode

The above implementation can support either or both prescription approaches.

Input options
-------------

lookupGenerator('alpha', 1, 'gamma', 2);  // arguments
lookupGenerator('alpha[1].gamma[2]');     // string
lookupGenerator('alpha[1]', 'gamma[2]');  // string arguments

Enter fullscreen mode Exit fullscreen mode

How does it work?

First we join all the strings together to form a single string with a dot separating each sequence. We then separate each property name and array subscript using a Regular Expression (RegExp) match. For a discussion of the power of RegExp please read this post of mine.

The array of segments resulting from the split operation can produce empty matches that need to be filtered out before they are presented to the reduce method as before. Finally we need to guard against the reduce method failing to locate a property or finding a null mid process and throwing an exception.

I hope you have found this supplement informative but please provide any related questions you have in the discussion section below and I will be happy to try to provide an answer.

If partial application is of interest, you might also be interested in my post on Currying.

Oldest comments (0)