繁体   English   中英

在撰写功能Ramda中访问数据

[英]Accessing data inside compose function Ramda

我正在使用Ramda来提供一些数据的结构。 但是,我一直无法访问compose内部的数据。

  1. 它应该映射level大于2的项目,但这不起作用

[keys, compose(map(map(find(propEq('level', > 2)))), values)]

  1. 我试图将typeChild内的所有项目保持为unique

这是用于测试的ramda控制台(不要跟随那里的链接,所以将不允许goo.gl链接): http : //dpaste.com/0SATTZK

const result = pipe(
  pluck('type'),
  groupBy(
    pipe(
      find(propEq('level', 1)),
      propOr('NoLevel', 'name'),
    )
  ),
  converge(
    zipWith(unapply(zipObj(['name', 'typeChild']))),
    [keys, compose(map(map(find(propEq('level', 2)))), values)]
  ),
);
result(data)

输入数据

[{
    "title": "Apple",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Tomato",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Potato",
    "type": [{"name": "Food", "level": 1}, {"name": "Vegetable", "level": 2}]
  }, {
    "title": "The Alchemist",
    "type": [{"name": "Entertainment", "level": 1}, { "name": "Book", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Entertainment", "level": 1}, {"name": "Movie", "level": 2}]
  }, {
    "title": "More facts",
    "type": [{"name": "Foo", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Bar", "level": 1}]
  }
];

所需的输出

[

{name: "Food", typechild: [{level: 2, name: "Fruit"}, {level: 2, name: "Vegetable"}]},
 {name: "Entertainment", typechild: [{level: 2, name: "Book"}, {level: 2, name: "Movie"}]},
 {name: "NoName", typechild: [{level: 2, name: "Foo"}]},
 {name: "Bar", typechild: []}
]

好吧,我将猜测您要寻找的东西。


给我看数据

首先,您确实需要演示输入数据的一部分。 我将其简化为:

const data =[{
    "title": "Apple",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Tomato",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Potato",
    "type": [{"name": "Food", "level": 1}, {"name": "Vegetable", "level": 2}]
  }, {
    "title": "The Alchemist",
    "type": [{"name": "Entertainment", "level": 1}, { "name": "Book", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Entertainment", "level": 1}, {"name": "Movie", "level": 2}]
  }, {
    "title": "More facts",
    "type": [{"name": "Foo", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Bar", "level": 1}]
  }
];

(请注意,我从每种类型中删除了color属性,因为它们似乎与讨论无关,但是它们不会改变任何内容。)

我从您的尝试中猜测,将需要这样的输出:

[
 {name: "Food", typechild: [{level: 2, name: "Fruit"}, {level: 2, name: "Vegetable"}]},
 {name: "Entertainment", typechild: [{level: 2, name: "Book"}, {level: 2, name: "Movie"}]},
 {name: "NoName", typechild: [{level: 2, name: "Foo"}]},
 {name: "Bar", typechild: []}
]

通过组成函数来解决这个问题

这是一种方法:

const levelEq = (n) => pipe(prop('level'), equals(n));
const topLevel = pipe(prop('type'), find(levelEq(1)));
const topLevelName = pipe(topLevel, propOr('NoName', 'name'));
const extract2ndLevel = pipe(pluck('type'), flatten, filter(levelEq(2)));

const convert = pipe(
  groupBy(topLevelName),
  map(extract2ndLevel),
  map(uniq),
  toPairs,
  map(zipObj(['name', 'typechild']))
);

convert(data); //=> (the first output format above)

(通常,对于那些单行代码,我将使用compose并颠倒顺序,而不是pipe ,但是我也不太喜欢在同一脚本中混合composepipe 。对于较长的convert函数,我绝对更喜欢使用pipe 。但是切换其中任何一个或将其组合都不会改变任何基本内容。)

关键是这是建立在功能组合之上的。 我没有尝试立即构建所有功能,而是编写了单独的函数来完成小的工作并将它们组合为更复杂的功能。

请注意,此代码将无法正常处理不良数据,因此更改该代码可能会很麻烦。

还要注意,在主要功能中,我一次只做一个小步骤。 我可以注释掉后续步骤,以查看每个步骤的结果。 如果R.tap ,我也可以使用R.tap

重新组合很少是个好主意

除了相对简单的levelEq之外,每个辅助功能仅使用一次。 因此,它们可以很容易地内联。 我们可以这样重写代码:

const convert = pipe(
  groupBy(pipe(prop('type'), find(pipe(prop('level'), equals(1))), propOr('NoName', 'name'))),
  map(pipe(pluck('type'), flatten, filter(pipe(prop('level'), gte(__, 2))), uniq)),
  toPairs,
  map(zipObj(['name', 'typechild']))
);

但是对我来说这是一个难以理解的混乱,我不会打扰。

更好的文档

如果您习惯了Hindley-Milnar样式类型注释 ,则可能有助于向这些函数添加类型签名,也许类似:

// Type :: {name: String, level: Int}

// :: Int -> (Type -> Bool)
const levelEq = (n) => pipe(prop('level'), equals(n));
// :: {type: [Type]} -> Type
const topLevel = pipe(prop('type'), find(levelEq(1)));
// :: {type: [Type]} -> String
const topLevelName = pipe(topLevel, propOr('NoName', 'name'));
// :: [{title: String, type: [Type}]}] -> [Type]
const extract2ndLevel = pipe(pluck('type'), flatten, filter(levelEq(2)));

// [{title: String, type: [Type]}] -> [{name: String, typechild: [Type]}]
const convert = pipe( /* ... */ )

(如果这些对您没有任何意义,请不要担心。)

更改输出格式

但是也许您真的想要这样的事情:

[
 {"name": "Food", "typechild": ["Fruit", "Vegetable"]}, 
 {"name": "Entertainment", "typechild": ["Book", "Movie"]}, 
 {"name": "NoName", "typechild": ["Foo"]}, 
 {"name": "Bar", "typechild": []}
]

原来这是一个简单的更改:

const convert = pipe(
  groupBy(topLevelName),
  map(extract2ndLevel),
  map(uniq),
  map(pluck('name')), // <--- A single addition
  toPairs,
  map(zipObj(['name', 'typechild']))
);

map优势

我们在最后一个片段中看到的一件事是一系列连续的map调用。 这些中的每一个都单独遍历该列表。 这样可以生成清晰的代码,但是如果在性能测试中发现此附加循环导致了问题,则可以利用与map相关联合成法则 ,该法则经过适当翻译后表示:

pipe(map(f), map(g)) ≍ map(pipe(f, g))

因此,您可以添加以下内容:

// :: [{title: String, type: [Type}]}] -> [String]
const foo = pipe(extract2ndLevel, uniq, pluck('name'));

并像这样重写主要功能:

// [{title: String, type: [Type]}] -> [{name: String, typechild: [Type]}]
const convert = pipe(
  groupBy(topLevelName),
  map(foo),
  toPairs,
  map(zipObj(['name', 'typechild']))
);

但是我不能为这个新函数想到一个好名字,这使我认为这不是一个很好的抽象。 仅当实际性能测试表明多次迭代是一个实际问题时,我才会选择这样做。

结论

函数式编程涉及很多事情,但是关键技术之一就是将所有内容不懈地分解为易于理解的部分。 这就是我尝试使用此解决方案的方法。 尽管我们可以打破这一点来创建几乎没有可读性的没有依赖性的单个函数( 上面的“ Recombining ...” )。 另一方面,这种方法很容易更改我们的方法( “更改输出格式” ),并在必要时解决性能问题( map优势” )。

哇,那应该是博客文章!


您可以在Ramda REPL上看到其中的大部分内容。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM