Search code examples
javascriptfunctional-programmingramda.jspointfree

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


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,5],
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?


Solution

  • 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')]) );