DEV Community

Cover image for FP Lenses enhanced
Tracy Gilmore
Tracy Gilmore

Posted on • Edited 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.

Top comments (10)

Collapse
 
javarobit profile image
javarobit • Edited

It would be great if the function also accepted an array:

lookupGenerator(["alpha", 1, "gamma", 2]);
lookupGenerator(["alpha[1].gamma[2]"]);
lookupGenerator(["alpha[1]", "gamma[2]"]);

Enter fullscreen mode Exit fullscreen mode
Collapse
 
tracygjg profile image
Tracy Gilmore • Edited

Hi Javarobit, There is no need to support an array because of the rest syntax.
In the examples you gave, if you really need to use an array, all you need to do is prefix the array with the spread syntax.

lookupGenerator(...["alpha", 1, "gamma", 2]);
lookupGenerator(...["alpha[1].gamma[2]"]);
lookupGenerator(...["alpha[1]", "gamma[2]"]);
Enter fullscreen mode Exit fullscreen mode

Alternatively, you could just use the elements of the array as separate arguments.
Thank you for your comments. Keep them coming.
Tracy

Collapse
 
javarobit profile image
javarobit

Great, otherwise I decided to use an expression like this:

function lookupGenerator(...arg) {
props = args.length == 1 ? (Array.isArray(args[0]) ? args[0] : [args[0]]) : args;
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
tracygjg profile image
Tracy Gilmore

You could probably simplify this using Array.flat.

Collapse
 
javarobit profile image
javarobit • Edited

Thank you for your comments. Keep them coming.
Tracy

You haven't written a single acticle on dev.to in 2024. Why?

Thread Thread
 
tracygjg profile image
Tracy Gilmore • Edited

Loads of reasons. Primarily because I have been too busy with work but also I write more for my own benefit than anyone. That said, I have always written posts so they are hopefully understandable by the widest possible readership. Thank you for taking the time to enquire and should you have any questions...

Thread Thread
 
javarobit profile image
javarobit • Edited

Thank you for taking the time to enquire and should you have any questions...

Why did you leave Medium.com? I couldn't find you there.

Thread Thread
 
tracygjg profile image
Tracy Gilmore • Edited

I left Medium for a couple of reasons.

  1. There were/are a growing number of posts that are plagiarised or just plain wrong.
  2. Fare too many posts (usually the good ones) were moved behind a pay wall so inaccessible to the wider community.

I also found the feedback I received to be extremely negative on the whole. The entire experience became unpleasant so I stopped and moved to Dev.to.
However, Dev.to is starting to exhibit many of the same problems as Medium, made worse by the inclusion of AI-generated content, which is often incorrect.

This is another reason why my posts on Dev.to have reduced.

Thread Thread
 
javarobit profile image
javarobit

And you deleted all your articles on medium.com ?

Thread Thread
 
tracygjg profile image
Tracy Gilmore

The weren't many to delete so I moved them to Dev.to, where I have written many more. Before Medium I tried LinkedIn but became unhappy with that forum.