Этого можно достичь, используя линзы (обходы), хотя, скорее всего, не стоит дополнительной сложности.
Идея состоит в том, что мы можем использовать R.traverse
с аппликативным экземпляром Const
введите как то, что можно комбинировать с объективом и объединить ноль или более целей вместе.
Тип Const
позволяет заключить значение, которое не изменяется при отображении (т. е. оно остается постоянным). Как нам объединить две постоянные величины вместе, чтобы поддержать аппликативную ap
? Мы требуем, чтобы значения констант имели моноидный экземпляр, то есть они являются значениями, которые можно объединить вместе и иметь некоторое значение, представляющее пустой экземпляр (например, два списка могут быть объединены с пустым списком, являющимся пустым экземпляром, два числа могут быть добавлены с помощью ноль - это пустое instace, et c.)
const Const = x => ({
value: x,
map: function (_) { return this },
ap: other => Const(x.concat(other.value))
})
Далее мы можем создать функцию, которая позволит нам комбинировать цели объектива различными способами, в зависимости от предоставленной функции, которая переносит целевые значения в некоторый экземпляр моноида.
const foldMapOf = (theLens, toMonoid) => thing =>
theLens(compose(Const, toMonoid))(thing).value
Эта функция будет использоваться как R.view
и R.over
, принимая линзу в качестве первого аргумента, а затем функцию для обёртывания цели в экземпляр моноида, который будет объединить значения вместе. Наконец, он принимает то, что вы хотите детализировать с помощью объектива.
Далее мы создадим простую вспомогательную функцию, которую можно использовать для создания нашего обхода, захватывая тип моноида, который будет использоваться для агрегирования конечная цель.
const aggregate = empty => traverse(_ => Const(empty))
Это печальная утечка, в которой нам нужно знать, как конечный результат будет агрегироваться при составлении обхода, вместо того, чтобы просто знать, что это то, что нужно пройти. Другие языки могут использовать типы stati c для вывода этой информации, но не повезло с JS без изменения определения линз в Ramda.
Учитывая, что вы упомянули, что вы хотите суммировать цели вместе мы можем создать экземпляр моноида, который будет делать именно это.
const Sum = x => ({
value: x,
concat: other => Sum(x + other.value)
})
Это просто говорит о том, что вы можете обернуть два числа вместе, и при объединении они создадут новый Sum
, содержащий значение их добавления вместе.
Теперь у нас есть все, что нам нужно, чтобы объединить все это вместе.
const sumItemTotals = order => foldMapOf(
compose(
lensProp('lineItems'),
aggregate(Sum(0)),
lensProp('total')
),
Sum
)(order).value
sumItemTotals({
lineItems: [
{ name: "A", total: 33 },
{ name: "B", total: 123 },
{ name: "C", total: 777 }
]
}) //=> 933
Если вы просто хотите извлечь список вместо суммирования их напрямую, мы можем использовать экземпляр monoid для вместо списков (например, [].concat
).
const itemTotals = foldMapOf(
compose(
lensProp('lineItems'),
aggregate([]),
lensProp('total')
),
x => [x]
)
itemTotals({
lineItems: [
{ name: "A", total: 33 },
{ name: "B", total: 123 },
{ name: "C", total: 777 }
]
}) //=> [33, 123, 777]