Point-free Function Composition with Ramda.js - javascript

I am using Ramda.js for selector functions, to access data in a Redux store. What I want is to define my selectors as functions not referencing the state that the selectors act on, like:
const getUserName = path(['user', 'name']);
const name = getUserName({
user: {
name: 'Some Name'
}
});
This is easy for simple selectors, but sometimes becomes a problem for composed selectors.
Here is an example, where some items needs to be resolved, referenced by their id on an object:
const getItemById = id => state => path(['items', id], state);
const getConnectedItemIds = obj => path(['items'], obj);
const getItemsFromObj = obj => state => {
const ids = getConnectedItemIds(obj);
return ids.map(id => getItemById(id)(state));
};
The first function can easily be expressed without reference to state, and the second function without obj, something I believe is called point-free style. But how to write the third function without state?
I am looking for how to rewrite the third function using Ramda, but also rules and procedures regarding this, such as (without knowing if its true):
All composed functions need to have state as their last argument to be able to pull it out in the final composition.

There are many good suggestions already here. Probably most important is the advice to use point-free only when it improves readability, and not as a goal on its own.
My answer does use a point-free version of your main function and of one of your helpers, but skips it for the other, where I think readability would suffer.
const getItemById = id => path(['items', id]);
const getConnectedItemIds = prop ('items');
const getItemsFromObj = pipe (
getConnectedItemIds,
map (getItemById),
juxt
)
const obj = {foo: 42, items: ['8', '17']}
const state = {bar: 1, items: {'6': 'a', '8': 'b', '14': 'c', '17': 'd', '25': 'e'}}
console .log (
getItemsFromObj (obj) (state)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {juxt, map, path, pipe, prop} = R </script>
The important function here is juxt, which applies an array of functions to the same values, returning an array of the results.
Here getItemById is simplified from the original, by removing the state point, but making this point-free can be done only at a cost in readability, as far as I can tell. Other had suggestions for this, all of which are fine, but as they pointed out, none were as readable as the above. My version looked like this:
const getItemById = compose (path, flip (append) (['items']));
// or pipe (flip (append) (['items']), path);
I don't think it's any better or worse than the suggestions in the other answers, but none of them is as easy to read as
const getItemById = id => path(['items', id]);
Finally, if those helper functions are not used elsewhere, I think it can actually improve readability to inline them:
const getItemsFromObj = pipe (
prop ('items'),
map (id => path(['items', id])),
juxt
)
Note, although I didn't use it here, I really do like customcommander's suggestion of propOr([], 'items') for getConnectedItemIds. It cleanly removes one potential point of failure.

I do like pointfree style. I think it forces you to think twice about the design of your functions, which is always a good thing. However it should never be a goal. Use it only when it makes sense.
This function is already good enough:
const getItemById = id => state => path(['items', id], state);
My only suggestion would be to use curry:
const getItemById = curry((id, state) => path(['items', id], state));
You could convert it into pointfree style though, but I don't think it's actually worth it:
const getItemById = useWith(path, [pair('items'), identity]);
Let's go back to your question.
We have these two functions:
const getItemById = curry((id, state) => path(['items', id], state));
const getConnectedItemIds = propOr([], 'items');
You could design a getItemsFromObj pointfree style like this:
const getItemsFromObj = useWith(flip(map), [getConnectedItemIds, flip(getItemById)]);
Or you could also simply do:
const getItemsFromObj = curry((obj, state) => {
const ids = getConnectedItemIds(obj);
const idFn = flip(getItemById)(state);
return map(idFn, ids);
});
Which version would I recommend? I don't know; here's a couple of ideas to think about:
Does it feel natural to you?
What's your team affinity with FP? Can you train them? Go easy if they're just starting
In six months, which version would you feel more comfortable dealing with?
One thing I'd suggest though is that you get familiar with the
Hindley-Milner type system. It's definitely time well invested.
Don't let anybody tell you that you're not doing functional programming correctly if you're not doing it pointfree style.

I think you could still write it in a point-free fashion,
readability, however, gets a bit compromised..
Hope it helps :)
/**
* #param {string} id
* #param {Object<string, *>} state
**/
const getItemById = R.useWith(R.prop, [
R.identity,
R.prop('items'),
]);
const getItemsFromObj = R.useWith(R.flip(R.map), [
R.pipe(R.prop('items'), R.map(getItemById)),
R.applyTo,
]);
const items = {
1: { id: 1, title: 'Hello World' },
2: { id: 2, title: 'Hello Galaxy' },
3: { id: 3, title: 'Hello Universe' },
};
const state = { items };
// this should only take 'world' and 'universe';
console.log(
'result',
getItemsFromObj({ items: [1, 3] }, state),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
Notes:
Ramda functions are all curried, so you do not need to declare arguments in tail position: obj => path(['items'], obj); is equal to path(['items']);
Being point-free helps to write small and focussed functions, but it should be balanced with composition readability

It's possible to make it both short and point free, but I'm not sure if it's more readable than the a function using variables.
const getItemsFromObj = R.useWith(R.props, [R.prop('items'), R.prop('items')])
I'm not using your getItemById here, since this case is ideal for R.props instead. If you really want to use both of your original functions, you could write it like this.
const getItemsFromObj = R.useWith(
R.flip(R.map), [getConnectedItemIds, R.flip(R.uncurryN(2, getItemById))]
)
The flip and uncurry is needed to reverse the getItemById function arguments from id -> state -> value into state -> id -> value

Related

In Javascript, is there an easyish way to get a chainable Array prepend operation like the reverse of Array.concat?

I'm doing array manipulation in Javascript, and I want to be able to chain operations with multiple calls to map, concat, etc.
const someAmazingArrayOperation = (list) =>
list
.map(transformStuff)
.sort(myAwesomeSortAlgorithm)
.concat([someSuffixElement])
.precat([newFirstElement])
.filter(unique)
But the problem I've run into is that Array.precat doesn't exist. (Think of Array.concat, but the reverse.)
I don't want to modify Array.prototype in my own code, for reasons. (https://flaviocopes.com/javascript-why-not-modify-object-prototype/)
I could totally use Array.concat and concatenate my array to the end of the prefix array and carry on. But that doesn't chain with the other stuff, and it makes my code look clunky.
It's kind of a minor issue because I can easily write code to get the output I want. But it's kind of a big deal because I want my code to look clean and this seems like a missing piece of the Array prototype.
Is there a way to get what I want without modifying the prototype of a built-in type?
For more about the hypothetical Array.precat, see also:
concat, but prepend instead of append
You could use Array#reduce with a function which takes the initialValue as array for prepending data.
const
precat = (a, b) => [...a, b],
result = [1, 2, 3]
.reduce(precat, [9, 8, 7]);
console.log(result)
If you don't want to modify Array.prototype, you can consider extends:
class AmazingArray extends Array {
precat(...args) {
return new AmazingArray().concat(...args, this);
}
}
const transformStuff = x => 2*x;
const myAwesomeSortAlgorithm = (a, b) => a - b;
const someSuffixElement = 19;
const newFirstElement = -1;
const unique = (x, i, arr) => arr.indexOf(x) === i;
const someAmazingArrayOperation = (list) =>
new AmazingArray()
.concat(list)
.map(transformStuff)
.sort(myAwesomeSortAlgorithm)
.concat([someSuffixElement])
.precat([newFirstElement])
.filter(unique);
console.log(someAmazingArrayOperation([9, 2, 2, 3]));
I don't want to modify Array.prototype in my own code, for reasons.
These reasons are good, but you can sidestep them by using a collision-safe property - key it with a symbol, not a name:
const precat = Symbol('precatenate')
Array.prototype[precat] = function(...args) {
return [].concat(...args, this);
};
const someAmazingArrayOperation = (list) =>
list
.map(transformStuff)
.sort(myAwesomeCompareFunction)
.concat([someSuffixElement])
[precat]([newFirstElement])
.filter(unique);

Check if object has only the given keys using Lodash or Underscore

Is there a Lodash or Underscore method which can find if an object has only the given keys of that object. I would like a Lodash or Underscore implementation even though this sounds trivial using native JS.
For example if my object looks like and assuming there is a lodash method named hasOnly
const obj = {
name: undefined,
age: 15,
school: 'Some school'
}
_.hasOnly(obj,['name','age']) //return false
_.hasOnly(obj,['name','age','city']) //return false
_.hasOnly(obj,['name','age','school']) //return true
I couldn't seem to find a way in the docs
Quick and dirty:
hasOnly = (obj, props) => _.isEqual(_.keys(obj).sort(), props.sort())
The sorting is done because we are comparing arrays.
As an alternative, one could turn both props and _.keys(obj) into objects where the props and _.keys(obj) are the keys, whereas the value is a dummy one, always the same, such as 1. The function to do so could be something like this:
make1ValuedObj = keys => _.zipObject(keys, Array(keys.length).fill(1))
Then one would pass those to _.isEqual without having to sort anything:
hasOnly = (obj, props) => _.isEqual(make1ValuedObj(_.keys(obj)), make1ValuedObj(props))
The reality is that a kind of "sorting" has to happen when you construct the objects, so I don't think there's a real advantage over the solution above.
The native solution will be faster in almost all cases:
const obj = {
name: undefined,
age: 15,
school: 'Some school'
}
const hasOnly = (obj,props) => {
var objProps = Object.keys(obj)
return objProps.length == props.length && props.every(p => objProps.includes(p))
}
console.log(hasOnly(obj,['name','age'])) //return false
console.log(hasOnly(obj,['name','age','city'])) //return false
console.log(hasOnly(obj,['name','age','school'])) //return true
Benchmarking this against the other answer using lodash shows the lodash solution to be 95% slower (on my machine)
Benchmarks: https://jsbench.me/r9kz2mwr9c/1
I think Enlico's answer is fine, but for completeness I'll mention another option which doesn't require sorting. This is based on comparing objects directy instead of comparing arrays of keys.
Note that the code below assumes the original Underscore. For Lodash, replace _.mapObject by _.mapValues.
// replace all properties by true to prevent costly recursion
const mask = obj => _.mapObject(obj, _.constant(true));
function hasOnly(obj, keys) {
const masked = mask(obj);
// compare obj to a trimmed version of itself
return _.isEqual(masked, _.pick(masked, keys));
}

How to "pass" extra data between chained maps and filters

This is mostly for academic interest, as I've managed to solve it an entirely different way, but, short story, what I want is, in pseudocode:
Foreach object in array1
Find matching otherObject in array2 // must exist and there's only 1
Find matching record in array3 // must exist and there's only 1
If record.status !== otherObject.status
push { otherObject.id, record.status } onto newArray
It intuitively seems to me there should be a way to do something with array1.filter(<some function>).map(<some other function>, but I can't get it to work in practice.
Here's a real-world example. This works:
function update(records) {
const filtered = records.filter((mcr) => {
const match = at._people.find((atr) => atr.email.toLowerCase() ===
mcr.email.toLowerCase());
return (match.subscriberStatus.toLowerCase() !==
mc.mailingList.find((listEntry) =>
listEntry.id === mcr.id).status.toLowerCase()
);
});
const toUpdate = filtered.map((mcr) => {
const match = at._people.find((atr) => atr.email.toLowerCase() ===
mcr.email.toLowerCase());
return ({ 'id': match.id,
'fields': {'Mailing List Status': mcr.subscriberStatus }
}
);
});
}
But what bums me out is the duplicated const match =. It seems to me that those could be expensive if at._people is a large array.
I naively tried:
function update(records) {
let match;
const toUpdate = records.filter((mcr) => {
match = at._people.find((atr) => atr.email.toLowerCase() ===
mcr.email.toLowerCase());
// return isDifferent?
return (match.subscriberStatus.toLowerCase() !==
mc.mailingList.find((listEntry) => listEntry.id === mcr.id).status.toLowerCase());
}).map((foundMcr) => {
return ({ 'id': match.id, 'fields': {'Mailing List Status': foundMcr.subscriberStatus } })
});
}
But (somewhat obviously, in retrospect) this doesn't work, because inside the map, match never changes — it's just always the last thing it was in the filter. Any thoughts on how to pass that match.id found in the filter on an entry-by-entry basis to the chained map? Or, really, any other way to accomplish this same thing?
If you were to only use .map and .filter() then you can avoid extra re-calculations later on in the chain if you do the following (generic steps):
.map() each item into a wrapper object that contains:
item from the array
calculate the extra data you would need in later steps
.filter() the wrapper objects based on the calculated data.
.map() the leftover results into the shape you wish, drawing on the original item and any of the calculated data.
In your case, this can mean that:
You do the find logic once.
Use the found items to discard some of the results.
Use the rest to generate a new array.
Here is the result with the callbacks extracted to make the map/filter/map logic clearer:
//takes a record and enriches it with `match` and `mailingStatus`
const wrapWithLookups = mcr => {
const match = at._people.find((atr) => atr.email.toLowerCase() ===
mcr.email.toLowerCase());
const mailingListStatus = mc.mailingList.find((listEntry) => listEntry.id === mcr.id).status;
return { match, mailingListStatus , mcr };
};
//filters based on match and mailingListStatus calculated fields
const isCorrectSubscriberStatus = ({match, mailingListStatus}) =>
match.subscriberStatus.toLowerCase() !== mailingListStatus .toLowerCase();
//converts to a new item based on mcr and match
const toUpdatedRecord = ({match, mcr}) => ({
'id': match.id,
'fields': {'Mailing List Status': mcr.subscriberStatus }
});
function update(records) {
return records
.map(wrapWithLookups)
.filter(isCorrectSubscriberStatus)
.map(toUpdatedRecord);
}
This saves the re-calculation of match and/or mailingStatus if they are needed later. However, it does introduce an entire new loop through the array just to collect them. This could be a performance concern, however, it is very easily remedied if you use lazy evaluated chain like what is provided by Lodash. The code adjustment to use that would be:
function update(records) {
return _(records) // wrap in a lazy chain evaluator by Lodash ->-+
.map(wrap) // same as before |
.filter(isCorrectSubscriberStatus) // same as before |
.map(toUpdatedRecord) // same as before |
.value(); // extract the value <-----------------------------+
}
Other libraries would likely have a very similar approach. In any case, lazy evaluation does not run once through the array for .map(), then another time for .filter(), then a third time for the second .map() but instead only iterates once and runs the operations as appropriate.
Lazy evaluation can be expressed through a transducer which is built on top of reduce(). For an example of how transducers work see:
How to chain map and filter functions in the correct order
Transducer flatten and uniq
Thus it is possible to avoid all the .map() and .filter() calls by simply doing one combined function and directly use .reduce(). However, I personally find that harder to reason about and more difficulty to maintain, than expressing the logic through a .map().filter().map() chain and then using lazy evaluation, if performance is needed.
Worth noting that the map() -> filter() -> map() logic does not need to used via a lazy chain. You can use a library like the FP distribution of Lodash or the vanilla Ramda that give you generic map() and filter() functions that can be applied to any list and combined with each other to avoid multiple repetitions again. Using Lodash FP this would be:
import map from 'lodash/fp/map';
import filter from 'lodash/fp/filter';
import flow from 'lodash/fp/flow';
function update(records) {
const process = flow(
map(wrapWithLookups),
filter(isCorrectSubscriberStatus),
map(toUpdatedRecord),
);
return process(records);
}
For Ramda the implementation would be the same - map() and filter() act the same across the two libraries, the only difference is that the composition function (flow() in Lodash) is called pipe in Ramda. It acts identically to flow():
pipe(
map(wrapWithLookups),
filter(isCorrectSubscriberStatus),
map(toUpdatedRecord),
)
For a deeper look at why you might want to avoid chaining and the alternative here, see the article on Medium.com: Why using _.chain is a mistake.
Here's how I would do this, if this can help:
let newArray = array1.filter((item) => {
let matching1 = array2.filter(matchingFunction)
let matching2 = array3.filter(matchingFunction)
return matching1?.status == matching2?.status;
})

Array method that does nothing

I like functional programming, it keeps my code, especially scopes, cleaner.
I found myself having a pretty heavy Array manipulation in my code, like this:
this.myArray = someArray
.slice(0, n)
.map(someFunction)
// more manipulation;
if (condition) {
this.myArray = this.myArray.reverse();
}
this.myArray = this.myArray
.reduce(anotherFunction, [])
// even more manipulation
Is there some built-in way to join the if to my functional chain? Something like:
this.myArray = someArray
.slice(0, n)
.map(someFunction)
// ... more manipulation
[condition ? 'reverse' : 'void']()
.reduce(anotherFunction, [])
// ... even more manipulation
The void() method doesn't exist. Is there an alternative? Is it popular approach to merge multiple calls to a single chain, even if that means calling methods that do nothing?
I know I can add my own method to Array:
Array.prototype.void = function () {
return this;
}
But that's not the point. Is there any standard/built-in way to achieve the same effect?
As neutral function, you could take
Array#concat, which returns a new array with old items, or
Array#slice, which returns as well a new array.
Is it popular approach to merge multiple calls to a single chain, even if that means calling methods that do nothing?
No. The usual approach is to split the chain, similar to how you wrote it in your first snippet, when there is an optional step. You wouldn't however repeatedly assign to this.myArray, you would use constant temporary variables:
const array1 = someArray.slice(0, n).map(someFunction) // more manipulation;
const array2 = condition ? array1.reverse() : array1;
this.myArray = array2.reduce(anotherFunction, []) // even more manipulation
That said, in functional programming that uses functions, not methods, you sometimes do find the approach of having a configurable chain. They don't need a void method on the object, they just use the identity function.
Example in Haskell:
let maybeReverse = if condition then reverse else identity
let myArray = fold anotherFunction [] $ maybeReverse $ map someFunction $ take n someArray
Example in JavaScript (where you don't have as many useful builtins and need to write them yourself):
const fold = (fn, acc, arr) => arr.reduce(fn, acc);
const reverse = arr => arr.reverse(); // add .slice() to make pure
const identity = x => x;
const map = (fn, arr) => arr.map(fn);
const take = (n, arr) => arr.slice(0, n);
const maybeReverse = condition ? reverse : identity;
const myArray = fold(anotherFunction, [], maybeReverse(map(someFunction, take(n, someArray)))));
Btw, in your particular example I wouldn't use reverse at all, but rather conditionally switch between reduce and reduceRight :-)
Another option is to switch reduce() with reduceRight() which wouldn't add an extra step at all for the case shown
this.myArray = someArray
.slice(0, n)
.map(someFunction)
[condition ? 'reduce' : 'reduceRight'](anotherFunction, [])

How can I improve my filtered Loop of Objects with lodash?

I am looping through issues in the data filtered by a condition.
const rowObjects = Object.keys(data).filter((list) => {
let issueRow = data[list];
let destroyFilter = list._destroy:
return !destroyFilter ? list : null;
}).
map((issue, key) => ({
console.log(key + 1)
}));
I am wondering if there is a way todo this with lodash?
I would do something like this, I just learned actually.
const filter = _.pickBy(data, (list) => {
return //whatever you want
});
Looking at your code, from what I can gather you have your data as an object literal, you then mark the key's you don't want with a _destroy tag.
You then want another object literal without the one's marked _destroy.
Below is a version that does that without lodash,.
There might be an easier way in lodash, not sure. But the ES6 way to me just looks so natural, and easy to follow. It could even be made to look simpler by splitting out bits into utility functions, eg. the last reduce could be used in other places were you want key / value, transformed back into an object literal.
ps. If you don't mind modifying the original object literal, this is even easier, and most likely better performance. Things like React I believe like deterministic data, so deleting in-place might not be an option.
const data = {
a: {
_destroy: true,
value: "destroy this"
},
b: {
value: "Keep Me!!"
}
};
const data2 =
Object.entries(data).
filter(([key, value]) => !value._destroy).
reduce((obj, [key, value]) => (obj[key] = value) && obj, {});
console.log(data2);

Categories