简体   繁体   English

使用Ramda.js的无点函数组合

[英]Point-free Function Composition with Ramda.js

I am using Ramda.js for selector functions, to access data in a Redux store. 我使用Ramda.js作为选择器函数,以访问Redux存储中的数据。 What I want is to define my selectors as functions not referencing the state that the selectors act on, like: 我想要的是将我的选择器定义为不引用选择器操作state函数,例如:

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

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

This is easy for simple selectors, but sometimes becomes a problem for composed selectors. 对于简单的选择器来说这很容易,但有时候组合选择器会成为一个问题。

Here is an example, where some items needs to be resolved, referenced by their id on an object: 这是一个示例,其中一些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));
};

The first function can easily be expressed without reference to state , and the second function without obj , something I believe is called point-free style. 第一个函数可以很容易地表达而不参考state ,而第二个函数没有obj ,我认为这个函数称为无点样式。 But how to write the third function without state ? 但如何编写没有state的第三个函数?

I am looking for how to rewrite the third function using Ramda, but also rules and procedures regarding this, such as (without knowing if its true): 我正在寻找如何使用Ramda重写第三个函数,还有关于此的规则和程序,例如(不知道它是否真实):

All composed functions need to have state as their last argument to be able to pull it out in the final composition. 所有组合函数都需要将state作为最后一个参数才能在最终组合中将其拉出。

There are many good suggestions already here. 这里有很多好的建议。 Probably most important is the advice to use point-free only when it improves readability, and not as a goal on its own. 可能最重要的是只有在提高可读性时才使用无点的建议,而不是单独作为目标。

My answer does use a point-free version of your main function and of one of your helpers, but skips it for the other, where I think readability would suffer. 我的回答确实使用了你的主要功能和你的一个助手的无点版本,但跳过另一个,我觉得可读性会受到影响。

 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> 

The important function here is juxt , which applies an array of functions to the same values, returning an array of the results. 这里的重要功能是juxt ,它将一组函数应用于相同的值,返回结果数组。

Here getItemById is simplified from the original, by removing the state point, but making this point-free can be done only at a cost in readability, as far as I can tell. 这里getItemById通过删除state点从原始getItemById中简化,但是只要我知道这一点,只需要以可读性为代价才能实现这一点。 Other had suggestions for this, all of which are fine, but as they pointed out, none were as readable as the above. 其他人对此提出了建议,所有这些都很好,但正如他们所指出的,没有一个像上面那样可读。 My version looked like this: 我的版本看起来像这样:

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

I don't think it's any better or worse than the suggestions in the other answers, but none of them is as easy to read as 我不认为它比其他答案中的建议更好或更差,但它们都不像阅读那么容易

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

Finally, if those helper functions are not used elsewhere, I think it can actually improve readability to inline them: 最后,如果那些辅助函数没有在别处使用,我认为它实际上可以提高内联它们的可读性:

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

Note , although I didn't use it here, I really do like customcommander's suggestion of propOr([], 'items') for getConnectedItemIds . 注意 ,虽然我没有在这里使用它,但我确实喜欢propOr([], 'items')对于getConnectedItemIdspropOr([], 'items')的建议。 It cleanly removes one potential point of failure. 它彻底消除了一个潜在的失败点。

I think you could still write it in a point-free fashion, readability, however, gets a bit compromised.. Hope it helps :) 我认为你仍然可以用无点的方式编写它,可读性,然而,有点妥协..希望它有帮助:)

 /** * @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> 


Notes: 笔记:

  1. Ramda functions are all curried, so you do not need to declare arguments in tail position: obj => path(['items'], obj); Ramda函数都是咖喱,所以不需要在尾位置申报参数: obj => path(['items'], obj); is equal to path(['items']); 等于path(['items']);
  2. Being point-free helps to write small and focussed functions, but it should be balanced with composition readability 无点有助于编写小型和集中的函数,但它应该与组合可读性相平衡

I do like pointfree style. 我喜欢无点风格。 I think it forces you to think twice about the design of your functions, which is always a good thing. 我认为这会迫使你对功能的设计三思而后行,这总是一件好事。 However it should never be a goal. 但它永远不应该是一个目标。 Use it only when it makes sense. 只有在有意义的时候才使用它。

This function is already good enough: 这个功能已经足够好了:

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

My only suggestion would be to use curry : 我唯一的建议是使用curry

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

You could convert it into pointfree style though, but I don't think it's actually worth it: 你可以把它转换成无点的风格,但我不认为它实际上是值得的:

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

Let's go back to your question. 让我们回到你的问题。

We have these two functions: 我们有这两个功能:

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

You could design a getItemsFromObj pointfree style like this: 你可以像这样设计一个getItemsFromObj pointfree样式:

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

Or you could also simply do: 或者您也可以简单地做:

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

Which version would I recommend? 我会推荐哪个版本? I don't know; 我不知道; here's a couple of ideas to think about: 这里有几点想法:

  1. Does it feel natural to you? 你感觉自然吗?
  2. What's your team affinity with FP? 你的团队与FP有什么关系? Can you train them? 你能训练他们吗? Go easy if they're just starting 如果他们刚刚开始就去
  3. In six months, which version would you feel more comfortable dealing with? 在六个月内,您会觉得哪个版本更舒服?

One thing I'd suggest though is that you get familiar with the Hindley-Milner type system. 我建议的一件事是你熟悉Hindley-Milner型系统。 It's definitely time well invested. 这绝对是投入时间的好时机。

Don't let anybody tell you that you're not doing functional programming correctly if you're not doing it pointfree style. 如果你没有采用无点风格,不要让任何人告诉你你没有正确地进行函数式编程。

It's possible to make it both short and point free, but I'm not sure if it's more readable than the a function using variables. 它可以使它既短又无点,但我不确定它是否比使用变量的函数更具可读性。

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

I'm not using your getItemById here, since this case is ideal for R.props instead. 我这里没有使用你的getItemById ,因为这个案例非常适合R.props If you really want to use both of your original functions, you could write it like this. 如果你真的想要使用两个原始函数,你可以这样写。

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

The flip and uncurry is needed to reverse the getItemById function arguments from id -> state -> value into state -> id -> value 需要翻转和取消将getItemById函数参数从id -> state -> value转换为state -> id -> value

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

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