[英]Point-free Function Composition with 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
作为最后一个参数才能在最终组合中将其拉出。
这里有很多好的建议。 可能最重要的是只有在提高可读性时才使用无点的建议,而不是单独作为目标。
我的回答确实使用了你的主要功能和你的一个助手的无点版本,但跳过另一个,我觉得可读性会受到影响。
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
点从原始getItemById
中简化,但是只要我知道这一点,只需要以可读性为代价才能实现这一点。 其他人对此提出了建议,所有这些都很好,但正如他们所指出的,没有一个像上面那样可读。 我的版本看起来像这样:
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
)
注意 ,虽然我没有在这里使用它,但我确实喜欢propOr([], 'items')
对于getConnectedItemIds
的propOr([], 'items')
的建议。 它彻底消除了一个潜在的失败点。
我认为你仍然可以用无点的方式编写它,可读性,然而,有点妥协..希望它有帮助:)
/** * @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>
obj => path(['items'], obj);
等于path(['items']);
我喜欢无点风格。 我认为这会迫使你对功能的设计三思而后行,这总是一件好事。 但它永远不应该是一个目标。 只有在有意义的时候才使用它。
这个功能已经足够好了:
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
pointfree样式:
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);
});
我会推荐哪个版本? 我不知道; 这里有几点想法:
我建议的一件事是你熟悉Hindley-Milner型系统。 这绝对是投入时间的好时机。
如果你没有采用无点风格,不要让任何人告诉你你没有正确地进行函数式编程。
它可以使它既短又无点,但我不确定它是否比使用变量的函数更具可读性。
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.