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.

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

9 Comments

This is nice, but it still depends on the free variable, source. I think a totally point-free solution would be pretty hairy.
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!
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.
@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.
@fredrik.hjarner: I'm impressed! I would never use it, but I'm impressed. Two composes, a useWith, and a converge. Wow!
|
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.

1 Comment

Thanks Scott Suayet. I have been reading a lot of your articles. I was trying to curry my functions from your article 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
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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.