Ramda.js - 如何从嵌套数组中查看多个值

Ramda.js - how to view many values from a nested array

我有这个代码:

import {compose, view, lensProp, lensIndex, over, map} from "rambda";

let order = {
    lineItems:[
        {name:"A", total:33},
        {name:"B", total:123},
        {name:"C", total:777},
    ]
};

let lineItems = lensProp("lineItems");
let firstLineItem = lensIndex(0);
let total = lensProp("total");

我的目标是获得所有 lineItems 中的所有 totals(因为我想对它们求和)。我是这样逐步解决这个问题的:

console.log(view(lineItems, order)); // -> the entire lineItems array
console.log(view(compose(lineItems, firstLineItem), order)); // -> { name: 'A', total: 33 }
console.log(view(compose(lineItems, firstLineItem, total), order)); // -> 33

但我想不出正确的表达式来取回总计数组

console.log(view(?????, order)); // -> [33,123,777]

这是我的问题 - ????? 在哪里?

我通过这样做来弥补我的无知:

let collector = [];
function collect(t) {
    collector.push(t);
}
over(lineItems, map(over(total, collect)), order);
console.log(collector); // -> [33,123,777]

但我相信 ramda 本地人知道如何做得更好。

您想在这里使用镜头有什么特别的原因吗?不要误会我的意思;镜片不错,但它们似乎对您的情况没有太大帮助。

最终这就是你试图完成的(据我所知):

map(prop('total'), order.lineItems)

您可以通过以下方式稍微重构一下:

const get_total = compose(map(prop('total')), propOr([], 'lineItems'));
get_total(order);

您可以使用 R.pluck 从对象数组中获取值数组:

const order = {"lineItems":[{"name":"A","total":33},{"name":"B","total":123},{"name":"C","total":777}]};

const result = R.pluck('total', order.lineItems);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>

可以使用镜头(遍历)实现此目的,但可能不值得额外的复杂性。

我们的想法是,我们可以将 R.traverseConst 类型的应用实例一起使用,作为可与镜头组合并将零个或多个目标组合在一起的东西。

Const 类型允许您包装一个在映射时不会改变的值(即它保持不变)。我们如何将两个常量值组合在一起以支持应用 ap?我们要求常量值有一个幺半群实例,这意味着它们是可以组合在一起的值,并且有一些值表示一个空实例(例如,两个列表可以连接起来,空列表是空实例,两个数字可以添加零是空实例等)

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.viewR.over 一样使用,接受一个镜头作为它的第一个参数,然后一个函数将目标包装在一个将值组合在一起的幺半群实例中.最后它接受了你想用镜头钻进去的东西。

接下来我们将创建一个简单的辅助函数,可用于创建我们的遍历,捕获将用于聚合最终目标的幺半群类型。

const aggregate = empty => traverse(_ => Const(empty))

这是一个不幸的泄漏,我们需要知道在编写遍历时最终结果将如何聚合,而不是简单地知道它是需要遍历的东西。其他语言可以使用静态类型来推断此信息,但如果不更改镜头在 Ramda 中的定义方式,JS 就没有这样的运气。

鉴于你提到你想将目标加在一起,我们可以创建一个 monoid 实例来执行此操作。

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]

根据您对customcommander 的回答的评论,我认为您可以将此写得相当简单。我不知道你是如何接收你的模式的,但是如果你可以将你的 lineItems 节点的路径转换成一个字符串数组,那么你就可以编写一个相当简单的函数:

const lineItemTotal = compose (sum, pluck ('total'), path)

let order = {
  path: {
    to: {
      lineItems: [
        {name: "A", total: 33},
        {name: "B", total: 123},
        {name: "C", total: 777},
      ]
    }
  }
}

console .log (
  lineItemTotal (['path', 'to', 'lineItems'], order)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {compose, sum, pluck, path} = R                       </script>

您可以围绕此包裹 curry 并使用 lineItemTotal (['path', 'to', 'lineItems']) (order) 调用生成的函数,可能会保存中间函数以供重用。