Идиоматическое `obj.value = f (obj)` в Рамде? - PullRequest
3 голосов
/ 19 марта 2019

R.evolve позволяет нам заменить свойства объекта результатом функции, примененной к текущему значению этого свойства:

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

Но я часто нахожу, что хочу добавить свойство, рассчитанное из нескольких свойств входного объекта:

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

Я придумал свою собственную простую служебную функцию:

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

... и я им часто пользуюсь:

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

Но частота, с которой я это использую, заставляет задуматься, почему это не так родной для Рамды, как и многие другие удобные функции. И это делает мне интересно, не хватает ли мне лучшей идиомы, которая достигает результата - то есть наращивания деталей в объекте путем добавления свойств, основанных на комбинации других свойств.

Есть ли идиоматическая конструкция функционального программирования, которую я должен использовать вместо этого?

Ответы [ 2 ]

3 голосов
/ 19 марта 2019

Лично я бы сделал это следующим образом:

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}
2 голосов
/ 19 марта 2019

Давайте начнем с признания того, что obj.value = f(obj) является изменяемым назначением и поэтому не является функциональной идиомой для начала.Это мышление в стиле императива на работе.

Хранение вычисленного значения как свойства вашего объекта в большинстве случаев является ошибкой.Если изменяется либо appleCount, либо orangeCount, то нет ничего, что обеспечивало бы целостность fruitCount.

fruitCount должна быть функцией , а не свойством.

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

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

Если бы мне пришлось угадывать, это поддельные данные и примерная проблема.В некоторых сценариях вычисленное значение имеет смысл (запоминание - это первая техника, которая приходит на ум), но эти случаи составляют исключение, а не правило.Вы говорите "абсолютная частота, с которой я использую это ..." , поэтому я бы поспорил, что вы сделаете это в большем количестве областей, чем должны.

И, как вы указали, Рамдане имеет встроенного для этого, так что это также должно указывать на то, что есть более традиционные способы решения такого рода проблем.


Объектно-ориентированный программист назначил бы это как вычисляемое свойство-

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

При написании функционального стиля мы оставляем концепции ООП за дверью.Начните думать с точки зрения функций, и ваши проблемы исчезнут -

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
...