Home > Mobile >  Ramda — extract two properties and append one to the other pointfree style
Ramda — extract two properties and append one to the other pointfree style

Time:02-23

I have some data in the form:

const data = {
    list: [1, 2, 3],
    newItem: 5
}

I want to make a function that appends the value of newItem to list resulting in this new version of data:

{
list: [1,2,3],
newItem: 5,
}

(Ultimately, I'd remove newItem after it's been moved into the list, but I'm trying to simplify the problem for this question).

I'm trying to do it using pointfree style and Ramda.js, as a learning experience.

Here's where I am so far:

const addItem = R.assoc('list',
    R.pipe(
        R.prop('list'),
        R.append(R.prop('newItem'))
    )
)

The idea is to generate a function that accepts data, but in this example the call to R.append also needs a reference to data, I'm trying to avoid explicitly mentioning data in order to maintain Pointfree style.

Is this possible to do without mentioning data?

CodePudding user response:

If I understand correctly you want to go from {x:3, y:[1,2]} to [1,2,3]. Here's one way:

const fn = compose(apply(append), props(['x', 'y']))

fn({x:3, y:[1,2]});
//=> [1,2,3]

CodePudding user response:

As the discussion on the answer from customcommander shows, there are two different possible interpretations.

If you want to just receive [1, 2, 3, 5], then you can do it as customcommander does, or the way I would choose:

const fn1 = lift (append) (prop ('newItem'), prop ('list'))

But if you wanted something like {list: [1, 2, 3, 5], newItem: 5}, then you might use the above inside applySpec and combine that with a merge, like this:

const fn2 = chain (mergeLeft, applySpec ({list: fn1}))

Here's a snippet:

const fn1 = lift (append) (prop ('newItem'), prop ('list')) 
const fn2 = chain (mergeLeft, applySpec ({list: fn1}))


const data = {list: [1, 2, 3], newItem: 5}

console .log (fn1 (data)) //=> [1, 2, 3, 5]
console .log (fn2 (data)) //=> {list: [1, 2, 3, 5], newItem: 5}
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {lift, append, prop, chain, mergeLeft, applySpec} = R </script>

This second one is a little unwieldy, once you inline fn1. It repeats the property list in two places, and that always bothers me. But I don't have a good solution at the moment.

I've several times wanted a combination of R.evolve and R.applySpec, which works on the outside like evolve, letting you specify only the properties which need to change, but whose transformation functions are given the whole input object, and not just the corresponding property.

With something like that, this might look like

const f3 = evolveSpec ({
  list: ({list, newItem}) => [...list, newItem]
})

or using the above:

const f3 = evolveSpec ({
  list: lift (append) (prop ('newItem'), prop ('list'))
})

I think this might be a useful candidate for inclusion in Ramda.

CodePudding user response:

const addItem = R.chain
  ( R.assoc('list') )
  ( R.converge(R.append, [R.prop('newItem'), R.prop('list')]) );

const data = {
    list: [1, 2, 3],
    newItem: 5
};

console.log(addItem(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>

And here is why:

First we can have a look at what the current addItem is supposed to look like when not point free:

const addItem = x => R.assoc('list')
  (
    R.pipe(
        R.prop('list'),
        R.append(R.prop('newItem')(x))
    )(x)
  )(x);

console.log(addItem({ list: [1, 2, 3], newItem: 5 }));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>

It takes some data and uses it in three places. We can refactor a bit:

const f = R.assoc('list');
const g = x => R.pipe(
        R.prop('list'),
        R.append(R.prop('newItem')(x))
    )(x)

const addItem = x => f(g(x))(x);

console.log(addItem({ list: [1, 2, 3], newItem: 5 }));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>

The x => f(g(x))(x) part might not be obvious immediately but looking at the list of common combinators in JavaScript it can be identified as S_:

Name # Haskell Ramda Sanctuary Signature
chain S_³ (=<<)² chain² chain² (a → b → c) → (b → a) → b → c

Thus x => f(g(x))(x) can be simplified pointfree to R.chain(f)(g).


This leaves the g which still takes one argument and uses it in two places. The ultimate goal is to extract two properties from an object and pass them to R.append(), this can be more easily (and pointfree) be expressed with R.converge() as:

const g = R.converge(R.append, [R.prop('newItem'), R.prop('list')]);

Substituting the f and g back gives

const addItem = R.chain
     ( R.assoc('list') )
     ( R.converge(R.append, [R.prop('newItem'), R.prop('list')]) );
  • Related