访问组合函数Ramda中的数据

时间:2017-06-21 07:39:21

标签: javascript ramda.js

我正在使用Ramda为某些数据提供结构。但是,我无法访问compose

中的数据
  1. 它应该映射level大于2的项目,但这不起作用
  2. [keys, compose(map(map(find(propEq('level', > 2)))), values)]

    1. 我正在尝试将typeChild内的所有项目保留为unique
    2. 这是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: []}
      ]
      

1 个答案:

答案 0 :(得分:4)

好的,我会猜测你在寻找什么。

显示数据

首先,您确实需要演示输入数据的某些部分。我把它简化为:

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)

(通常对于那些单行,而不是pipe,我会使用compose,并反转顺序,但我也不太喜欢混合compose和{{ 1}}在同一个脚本中。对于较长的pipe函数,我绝对更喜欢pipe。但是切换其中任何一个,或者将它们组合起来都不会改变任何必要的东西。)

关键是这是基于功能组合。我不是一次尝试构建它,而是编写单独的函数来完成小型工作,并将它们组合成更复杂的工作。

请注意,此代码不能正常处理错误数据,而更改为执行此操作可能是一项重要的工作。

另请注意,在main函数中,我一次只做一小步。我可以注释掉后续步骤,以查看每个步骤的结果。如果我喜欢的话,我也可以使用R.tap

重新组合很少是一个好主意

除了相对简单的convert之外,每个辅助函数只使用一次。所以他们可以很容易地内联。我们可以像这样重写这段代码:

levelEq

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

更好的文档

如果您习惯Hindley-Milnar style type annotation,可能有助于为这些函数添加类型签名,可能类似于:

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']))
);

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

更改输出格式

但也许你真的想要这样的东西:

// 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次调用。每个都分别在列表上循环。这样可以获得干净的代码,但是如果在性能测试中,您发现这个额外的循环导致了您的问题,那么您可以利用composition law associated with 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'])) ); 的优点“)。

哇,这应该是一篇博文!

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