Ramda.js的无点函数组合

时间:2019-07-19 09:18:43

标签: javascript redux functional-programming ramda.js

我将Ramda.js用于选择器功能,以访问Redux存储中的数据。我想要的是将选择器定义为不引用选择器所作用的state的函数,例如:

const getUserName = path(['user', 'name']);

const name = getUserName({
  user: {
    name: 'Some Name'
  }
});

这对于简单的选择器来说很容易,但是对于组合选择器有时会成为问题。

这是一个示例,其中一些items需要解析,并由其id在对象上引用:

const getItemById = id => state => path(['items', id], state);

const getConnectedItemIds = obj => path(['items'], obj);

const getItemsFromObj = obj => state => {
    const ids = getConnectedItemIds(obj);
    return ids.map(id => getItemById(id)(state));
};

第一个函数可以很容易地表达而无需引用state,第二个函数可以不包含obj,我认为这被称为无点样式。但是,如何在没有state的情况下编写第三个函数呢?

我正在寻找如何使用Ramda重写第三个函数,以及与此有关的规则和过程,例如(不知道其正确性):

所有组合函数都必须以state作为其最后一个参数,以便能够将其从最终组合中提取出来。

4 个答案:

答案 0 :(得分:4)

这里已经有很多好的建议。可能最重要的建议是仅在提高可读性时才使用无点建议,而不是将其作为目标。

我的回答的确使用了您的主要功能和一个助手的无点版本,但是我认为它会影响可读性。

const getItemById = id => path(['items', id]);

const getConnectedItemIds = prop ('items');

const getItemsFromObj = pipe (
  getConnectedItemIds, 
  map (getItemById), 
  juxt
)

const obj = {foo: 42, items: ['8', '17']}
const state = {bar: 1, items: {'6': 'a', '8': 'b', '14': 'c', '17': 'd', '25': 'e'}}

console .log (
  getItemsFromObj (obj) (state)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {juxt, map, path, pipe, prop} = R                      </script>

这里的重要函数是juxt,它将一个函数数组应用于相同的值,并返回结果数组。

这里,getItemById通过删除state点而被简化,但是据我所知,使此无点仅以牺牲可读性为代价。其他人对此提出了建议,所有这些都很好,但是正如他们指出的那样,没有一个比上面的可读性高。我的版本看起来像这样:

const getItemById = compose (path, flip (append) (['items']));
//  or              pipe (flip (append) (['items']), path);

我认为这并不比其他答案中的建议更好或更糟,但它们都不比它容易读

const getItemById = id => path(['items', id]);

最后,如果这些辅助功能未在其他地方使用,我认为实际上可以提高内联它们的可读性:

const getItemsFromObj = pipe (
  prop ('items'), 
  map (id => path(['items', id])), 
  juxt
)

注意,尽管我在这里没有使用它,但我确实很喜欢customcommander对propOr([], 'items')的{​​{1}}的建议。它彻底清除了潜在的故障点。

答案 1 :(得分:2)

我认为您仍然可以毫无意义地编写它, 但是,可读性受到了影响。 希望对您有所帮助:)

/**
 * @param {string} id
 * @param {Object<string, *>} state
**/
const getItemById = R.useWith(R.prop, [
  R.identity,
  R.prop('items'),
]);

const getItemsFromObj = R.useWith(R.flip(R.map), [
  R.pipe(R.prop('items'), R.map(getItemById)),
  R.applyTo,
]);


const items = {
  1: { id: 1, title: 'Hello World' },
  2: { id: 2, title: 'Hello Galaxy' },
  3: { id: 3, title: 'Hello Universe' },
};

const state = { items };

// this should only take 'world' and 'universe';
console.log(
  'result', 
  getItemsFromObj({ items: [1, 3] }, state),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>


注意:

  1. Ramda函数都是库里函数,因此您不需要不需要在结尾位置声明参数:obj => path(['items'], obj);等于path(['items']);
  2. 不受限制有助于编写小型且集中的函数,但应与组合可读性保持平衡

答案 2 :(得分:2)

我喜欢自由风格。我认为这迫使您对功能设计进行三思,这总是一件好事。但是,绝不要成为目标。仅在有意义时使用它。

此功能已经足够好了

const getItemById = id => state => path(['items', id], state);

我唯一的建议是使用curry

const getItemById = curry((id, state) => path(['items', id], state));

虽然您可以将其转换为无点样式,但是我认为这实际上不值得:

const getItemById = useWith(path, [pair('items'), identity]);

让我们回到您的问题。

我们具有以下两个功能:

const getItemById = curry((id, state) => path(['items', id], state));
const getConnectedItemIds = propOr([], 'items');

您可以这样设计getItemsFromObj无点样式:

const getItemsFromObj = useWith(flip(map), [getConnectedItemIds, flip(getItemById)]);

或者您也可以简单地这样做:

const getItemsFromObj = curry((obj, state) => {
  const ids = getConnectedItemIds(obj);
  const idFn = flip(getItemById)(state);
  return map(idFn, ids);
});

我会推荐哪个版本?我不知道;这里有一些想法可供考虑:

  1. 您觉得自然吗?
  2. 您的团队对FP有何亲和力?你可以训练他们吗?如果他们只是开始就轻松
  3. 六个月后,您会更喜欢哪个版本?

我建议的一件事是,您应该熟悉 Hindley-Milner类型系统。肯定是时间投入了。

不要让任何人告诉您,如果您没有按照功能进行编程,那么您将不能正确地进行函数式编程。

答案 3 :(得分:1)

可以使它短而无点,但是我不确定它是否比使用变量的a函数更具可读性。

const getItemsFromObj = R.useWith(R.props, [R.prop('items'), R.prop('items')])

我在这里不使用您的getItemById,因为这种情况最适合R.props。如果您确实要使用两个原始功能,则可以这样编写。

const getItemsFromObj = R.useWith(
  R.flip(R.map), [getConnectedItemIds, R.flip(R.uncurryN(2, getItemById))]
)

需要翻转和毫不费力地将getItemById函数参数从id -> state -> value转换为state -> id -> value