0

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.

Dr G.
  • 1,298
  • 9
  • 17
nzaleski
  • 433
  • 5
  • 14

3 Answers3

2

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

fredrik.hjarner
  • 715
  • 8
  • 22
  • 2
    This is nice, but it still depends on the free variable, `source`. I think a totally point-free solution would be pretty hairy. – Scott Sauyet Jan 03 '18 at 18:31
  • Ah, you are right. Yes that would probably look like a mess. – fredrik.hjarner Jan 03 '18 at 18:49
  • 1
    Scott's comment about this being better suited for non point-free is probably true (I think you agree with that). But I asked for it to be point-free so I am going to accept this answer. Thanks! – nzaleski Jan 03 '18 at 19:21
  • 1
    No problem. Thanks :) Yes, since point-free programming is trending there are a lot of people going overboard with it. Sometimes it makes code look better but most often it does not. I do think that hardcore point-free programming is very good practice though. – fredrik.hjarner Jan 03 '18 at 19:25
  • @ScottSauyet can you explain what the risk/problem with it depending on the free variable `source` is? – nzaleski Jan 06 '18 at 02:01
  • 1
    @nzaleski: one of the key ideas in functional programming is to use `pure` functions, ones which depend only on their inputs and always return the same results for the same inputs (A pure function must also not do anything other than return that result -- no side effects.). It's a very powerful and useful technique, although this comment is too short to really explain why. When you depend on a value from your environment, your function is no longer pure. It's harder to test, harder to reason about, and this impurity affects every function which depends on it. – Scott Sauyet Jan 07 '18 at 21:40
  • 1
    @fredrik.hjarner: I'm impressed! I would never use it, but I'm impressed. Two `compose`s, a `useWith`, and a `converge`. Wow! – Scott Sauyet Jan 07 '18 at 21:41
  • 1
    @fredrik.hjarner: If I'm not mistaken, you can replace that second composition with `eqProps('name')`. That might make it a little more approachable. – Scott Sauyet Jan 07 '18 at 21:50
  • @ScottSauyet thank you for the clarification. I have been reading a lot about pure functions and the concept feels right to me (just one of the many things that feels right with the functional approach to me). However, I never read about the free variable specifically. So now I am reading into that :) – nzaleski Jan 08 '18 at 00:13
2

It's pretty straightforward to do this without worrying about point-free:

const {curry, assoc, prop, find, eqProps} = R;

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

const companies = [
  {shortName: 'AAA Cooper', name: 'AAA COOPER TRANSPORTATION' },
  {shortName: 'Old Dominion', name: 'OLD DOMINION FREIGHT LINE, INC'},
  {shortName: 'Road Runner', name: 'ROADRUNNER  TRANSPORTATION SERVICES'},
  {shortName: 'Flintstone', name: 'FRED FLINTSTONE HAULING'}
]

const [aaa, odfl, rr, ff] = companies

const addCost = curry((costs, company) => assoc(
  'cost', 
  prop('cost', find(eqProps('name', company), costs)), 
  company
))

console.log(addCost(kdlJsonObj)(rr))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

While this could be made point-free, the result would likely be much less readable. I try to think of point-free as a tool to use when (and only when) it increases readability, but your mileage may vary.

This also does not do any real error-checking. If the matching value is not found, it just adds cost: undefined.

Also note how fragile this is. There is a difference in spelling between the two versions of AAA in the post. If they are not generated from the same source, you could have issues like this all over if you're depending on a match between long strings. I don't have particular suggestions for this, but it's worth investigating.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • 1
    Thanks Scott Suayet. I have been reading a lot of your articles. I was trying to curry my functions from your article http://fr.umio.us/favoring-curry/. I appreciate the non point-free example and explanation. I agree with you and will probably not use point-free in this scenario. I am grateful for your example and @taz0k point-free example as a learning experiment – nzaleski Jan 03 '18 at 19:15
0

@Scott Sauyet I updated your a little for a couple of reasons. 1) I am on NetSuite and they use Rhino 6 which is not using ES6 spec. 2) I am new to this so although I like your very concise style I broke mine up a little so I could better understand it. Thank you for your help.

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

 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'}


var addCost = function(costs, company) {
  var comp = R.find(R.eqProps('name', company), costs)
  var cost = R.prop('cost', comp)
  return R.assoc('cost', cost, company)
}

var addCostCurried = R.curry(addCost)(kdlJsonObj)
var rrAssocatied = addCostCurried(rr)
var odflAssocatied = addCostCurried(odlf)
var aaaAssocatied = addCostCurried(aaa)
nzaleski
  • 433
  • 5
  • 14