Search code examples
javascriptramda.jsdeclarativepointfree

Compose Ramda Functions


Hi am just learning the Ramda library and loving it. I am trying to practice some functional concepts like curry and immutability. Below I have a little code that is basically trying to assoc the value from one object and copy that to another object. The first object kdlJsonObj has the cost value, that I would like to append that to another object

//object from API
var kdlJsonObj = [
  {name: 'AAA COOPER TRANSPORTATION', cost: 33},
  {name: 'OLD DOMINION FREIGHT LINE, INC', cost: 22},
  {name: 'ROADRUNNER  TRANSPORTATION SERVICES', cost: 31}
]

// objects to assoc 
var aaa = {shortName: 'AAA Cooper', name: 'AAA COOPER  TRANSPORTATION' }
var odlf = {shortName: 'Old Dominion', name: 'OLD DOMINION FREIGHT LINE, INC'}
var rr = {shortName: 'Road Runner', name: 'ROADRUNNER  TRANSPORTATION SERVICES'}

// Ramda functions that I would like to compose    
var namePropEq = R.propEq('name')
var namePropEqAAA = namePropEq('AAA COOPER TRANSPORTATION')
var findAAA = R.find(namePropEqAAA, kdlJsonObj) 
var costProp = R.prop('cost')
var costAAA = costProp(findAAA)
var assocCost = R.assoc('cost')
var assocCostAAA = assocCost(costAAA)(aaa)
assocCostAAA // => {shortName: "AAA Cooper", name: "AAA COOPER TRANSPORTATION", cost: 33}

I would like to be able to compose these set of function make it a more point-free style of coding where no data is provided until I make the call. Ideally it would be something like var assocCostAAA = composeAssoc(namePropEqAAA)(aaa) and I could just call one function. I am not sure it is possible to compose this function because of the arity rules

var composeAssoc = R.compose(
   R.assoc('cost'),
   R.find(name, kdlJsonObj),   // has two arity so i believe this is not correct
   R.propEq(name))

I am open to doing it different ways. Such as using Ramda functions like R.pluck,R.filter maybe even R.lens. But I would love for it to be a composed/declarative function.


Solution

  • I hope that there is a more elegant way, but this is point-free:

    const source = [
      { name: 'Aaa', cost: 1 },
      { name: 'Bee', cost: 2 },
      { name: 'Cee', cost: 3 },
    ];
    
    const target = { shortName: 'A', name: 'Aaa' };
    
    const func =
      R.chain(
        R.assoc('cost'),          // ('cost', 1, target)  ->  output
        R.compose(
          R.prop('cost'),         // ('cost', {name: 'Aaa', cost: 1})  ->  1
          R.compose(
            R.find(R.__, source), // (predicate, source)  ->  {name: 'Aaa', cost: 1}
            R.compose(
              R.propEq('name'),   // ('name', 'Aaa' )  ->  predicate
              R.prop('name'),     // ('name', target)  ->  'Aaa'
            ),
          ),
        ),
      );
    
    const targetWithCost = func(target);
    

    outputs: {"cost": 1, "name": "Aaa", "shortName": "A"}

    Run with Ramda REPL here!!

    Oh yes... this is a little bit better:

    const func =
      R.chain(
        R.assoc('cost'),        // ('cost', 1, target)  ->  :)
        R.compose(
          R.prop('cost'),       // ('cost', {name: 'Aaa', cost: 1})  ->  1
          R.find(R.__, source), // (predicate, source)  ->  {name: 'Aaa', cost: 1}
          R.eqProps('name'),    // ('name', target)  ->  predicate
        ),
      );
    

    outputs: {"cost": 1, "name": "Aaa", "shortName": "A"}

    Run with Ramda REPL here!!

    As Scott pointed out my solution was not completely point-free, so as an experiment I came up with this fully point-free code:

    const func =
      R.converge(
        R.assoc('cost'),      // ('cost', 1, target)  -> {shortName: 'A', name: 'Aaa', cost: 1}
        [
          R.useWith(          // (target, source)  ->  1
            R.compose(        // (predicate, source)  ->  1
              R.prop('cost'), // ('cost', {name: 'Aaa', cost: 1})  ->  1
              R.find,         // (predicate, source)  ->  {name: 'Aaa', cost: 1}
            ),
            [
              R.eqProps('name'), // ('name', target)  ->  predicate
              R.identity,     // source  ->  source
            ],
          ),
          R.identity,         // (target, source)  ->  target
        ],
      );
    
    const targetWithCost = func(target, source);
    

    Run it here in the Ramda REPL