Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Point-free Function Composition with Ramda.js

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.

like image 611
William Avatar asked Aug 31 '25 03:08

William


1 Answers

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.

like image 54
Scott Sauyet Avatar answered Sep 02 '25 18:09

Scott Sauyet