Idiomatic `obj.value = f(obj)` in Ramda?

时间:2019-03-19 14:51:34

标签: functional-programming ramda.js

R.evolve lets us replace object properties with the result of a function applied to that property's current value:

R.evolve({ count: R.inc }, { count: 1 })
   == { count: 2 }

But I frequently find I want to add a property calculated from multiple properties of input object:

assocFruitTotal({ appleCount: 5, orangeCount: 3 })
  == { appleCount: 5, orangeCount: 3, fruitCount: 8 }

I came up with my own simple utility function:

const assocDerived = R.curry(
   (name, f, obj) => ({
      ...obj,
      [name]: f(obj)
   });

... and I use it a lot:

const sumFruit = R.pipe(
   R.props(['appleCount', 'orangeCount']),
   R.sum);
const assocFruitTotal = assocDerived('fruitCount', sumFruit);

But the sheer frequency with which I use this makes me wonder why it's not native to Ramda, as so many other convenient functions are. And that makes me wonder whether I'm missing a better idiom that achieves the outcome -- that is, building up detail in an object by adding properties based upon combinations of other properties.

Is there an idiomatic functional programming construct I should be using instead?

2 个答案:

答案 0 :(得分:3)

我个人会这样:

const fruitCount = applySpec({fruitCount: compose(sum, values)})

fruitCount({apple: 5, orange: 3})
//=> {"fruitCount": 8}

const withFruitCount = converge(mergeRight, [identity, fruitCount]);

withFruitCount({apple: 5, orange: 3});
//=> {"apple": 5, "fruitCount": 8, "orange": 3}

如果要从总数中排除非计数属性,则可以使用pickBy

const pickCount = pickBy(flip(includes('Count')));

pickCount({appleCount: 5, orangeCount: 3, foo: 'bar'});
//=> {"appleCount": 5, "orangeCount": 3}

答案 1 :(得分:2)

让我们开始认识到obj.value = f(obj)是一个可变的赋值,因此不是一开始就有用的习语。这是工作中的命令式思维。

在大多数情况下,将计算值作为属性存储在对象上是错误的。如果appleCountorangeCount发生更改,则没有任何东西可以强制fruitCount的完整性。

fruitCount应该是函数,而不是属性。

const fruitCount =
  pipe
    ( props ([ 'appleCount', 'orangeCount' ])
    , sum
    )

 fruitCount ({ appleCount: 1, orangeCount: 3 }) // 4
 fruitCount ({ appleCount: 5, orangeCount: 3 }) // 8

如果我不得不猜测,这是虚假数据和示例问题。在某些情况下,计算值确实有意义(记忆化是我想到的第一种技术),但是这些情况构成了例外,而不是规则。您说“我使用此频率的频率非常高……” ,所以我建议您在更多的范围内使用它。

正如您所指出的那样,Ramda对此没有内置的功能,因此这应进一步表明存在解决此类问题的更多常规方法。


面向对象的程序员会将其分配为计算属性-

const FruitData = function (apples = 0, oranges = 0)
{ this.apples = apples
  this.oranges = oranges
}

Object.defineProperty
  ( FruitData.prototype
  , 'fruitCount'
  , { get () { return this.apples + this.oranges } }
  )
  
const f =
  new FruitData (3, 4)
  
console .log (f.fruitCount) // 7

在编写功能样式时,我们将OOP概念留在门外。开始思考功能,问题就会消失-

const FruitData = (apples = 0, oranges = 0) =>
  ({ apples, oranges })

const appleCount = fd =>
  fd.apples

const orangeCount = fd =>
  fd.oranges

const fruitCount = fd =>
  appleCount (fd) + orangeCount (fd)

console .log (fruitCount (FruitData (10, 3))) // 13