It is possible to achieve this using lenses (traversals), though will likely not be worth the additional complexity.
The idea is that we can use R.traverse with the applicative instance of a Const type as something that is composable with a lens and combines zero or more targets together.
The Const type allows you to wrap up a value that does not change when mapped over (i.e. it remains constant). How do we combine two constant values together to support the applicative ap? We require that the constant values have a monoid instance, meaning they are values that can be combined together and have some value representing an empty instance (e.g. two lists can be concatenated with the empty list being the empty instance, two numbers can be added with zero being the empty instace, etc.)
const Const = x => ({
value: x,
map: function (_) { return this },
ap: other => Const(x.concat(other.value))
})
Next we can create a function that will let us combine the lens targets in different ways, depending on the provided function that wraps the target values in some monoid instance.
const foldMapOf = (theLens, toMonoid) => thing =>
theLens(compose(Const, toMonoid))(thing).value
This function will be used like R.view and R.over, accepting a lens as its first argument and then a function for wrapping the target in an instance of the monoid that will combine the values together. Finally it accepts the thing that you want to drill into with the lens.
Next we'll create a simple helper function that can be used to create our traversal, capturing the monoid type that will be used to aggregate the final target.
const aggregate = empty => traverse(_ => Const(empty))
This is an unfortunate leak where we need to know how the end result will aggregated when composing the traversal, rather than simply knowing that it is something that needs to be traversed. Other languages can make use of static types to infer this information, but no such luck with JS without changing how lenses are defined in Ramda.
Given you mentioned that you would like to sum the targets together, we can create a monoid instance that does exactly that.
const Sum = x => ({
value: x,
concat: other => Sum(x + other.value)
})
This just says that you can wrap two numbers together and when combined, they will produce a new Sum containing the value of adding them together.
We now have everything we need to combine it all together.
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
If you just wanted to extract a list instead of summing them directly, we could use the monoid instance for lists instead (e.g. [].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]