Ramda.js 的无点函数组合
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
作为它们的最后一个参数才能在最终组合中提取它。
我认为你仍然可以用 point-free 的方式来写它,
可读性,然而,得到一点妥协..
希望对您有所帮助:)
/**
* @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>
备注:
- Ramda 函数都是柯里化的,所以你 不需要 需要在尾部位置声明参数:
obj => path(['items'], obj);
等于 path(['items']);
- 成为point-free有助于编写小而集中的功能,但它应该与组合可读性保持平衡
我喜欢pointfree风格。我认为这会迫使您对函数的设计三思而后行,这总是一件好事。然而,它 永远不会 成为目标。仅在有意义时使用它。
这个功能已经够用了:
const getItemById = id => state => path(['items', id], state);
我唯一的建议是使用 curry
:
const getItemById = curry((id, state) => path(['items', id], state));
虽然你可以将它转换成 pointfree 风格,但我认为它实际上不值得:
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);
});
我会推荐哪个版本?我不知道;这里有几个想法可供考虑:
- 你觉得自然吗?
- 您的团队对 FP 的亲和力如何?你能训练他们吗?如果他们刚刚开始,请放轻松
- 六个月后,您觉得使用哪个版本会更自如?
不过我建议您熟悉
Hindley-Milner 类型系统。这绝对是投资的时间。
不要让任何人告诉你,如果你没有使用 pointfree 风格,你就没有正确地进行函数式编程。
可以让它既短又无点,但我不确定它是否比使用变量的函数更具可读性。
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))]
)
需要 flip 和 uncurry 来将 getItemById
函数参数从 id -> state -> value
反转为 state -> id -> value
这里已经有很多好的建议。可能最重要的是建议仅在提高可读性时才使用 point-free,而不是单独作为目标。
我的回答确实使用了 point-free 版本的主要功能和你的一个助手,但跳过另一个版本,我认为可读性会受到影响。
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
点,但是使这个 point-free 只能以牺牲可读性为代价来完成,就我而言告诉。其他人对此有建议,所有这些都很好,但正如他们指出的那样,none 与上面的一样可读。我的版本是这样的:
const getItemById = compose (path, flip (append) (['items']));
// or pipe (flip (append) (['items']), path);
我不认为它比其他答案中的建议更好或更差,但其中 none 与
一样易于阅读
const getItemById = id => path(['items', id]);
最后,如果那些辅助函数没有在其他地方使用,我认为内联它们实际上可以提高可读性:
const getItemsFromObj = pipe (
prop ('items'),
map (id => path(['items', id])),
juxt
)
注意,虽然我没有在这里使用它,但我真的很喜欢customcommander对propOr([], 'items')
对getConnectedItemIds
的建议。它彻底消除了一个潜在的故障点。
我正在使用 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
作为它们的最后一个参数才能在最终组合中提取它。
我认为你仍然可以用 point-free 的方式来写它, 可读性,然而,得到一点妥协.. 希望对您有所帮助:)
/**
* @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>
备注:
- Ramda 函数都是柯里化的,所以你 不需要 需要在尾部位置声明参数:
obj => path(['items'], obj);
等于path(['items']);
- 成为point-free有助于编写小而集中的功能,但它应该与组合可读性保持平衡
我喜欢pointfree风格。我认为这会迫使您对函数的设计三思而后行,这总是一件好事。然而,它 永远不会 成为目标。仅在有意义时使用它。
这个功能已经够用了:
const getItemById = id => state => path(['items', id], state);
我唯一的建议是使用 curry
:
const getItemById = curry((id, state) => path(['items', id], state));
虽然你可以将它转换成 pointfree 风格,但我认为它实际上不值得:
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);
});
我会推荐哪个版本?我不知道;这里有几个想法可供考虑:
- 你觉得自然吗?
- 您的团队对 FP 的亲和力如何?你能训练他们吗?如果他们刚刚开始,请放轻松
- 六个月后,您觉得使用哪个版本会更自如?
不过我建议您熟悉 Hindley-Milner 类型系统。这绝对是投资的时间。
不要让任何人告诉你,如果你没有使用 pointfree 风格,你就没有正确地进行函数式编程。
可以让它既短又无点,但我不确定它是否比使用变量的函数更具可读性。
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))]
)
需要 flip 和 uncurry 来将 getItemById
函数参数从 id -> state -> value
反转为 state -> id -> value
这里已经有很多好的建议。可能最重要的是建议仅在提高可读性时才使用 point-free,而不是单独作为目标。
我的回答确实使用了 point-free 版本的主要功能和你的一个助手,但跳过另一个版本,我认为可读性会受到影响。
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
点,但是使这个 point-free 只能以牺牲可读性为代价来完成,就我而言告诉。其他人对此有建议,所有这些都很好,但正如他们指出的那样,none 与上面的一样可读。我的版本是这样的:
const getItemById = compose (path, flip (append) (['items']));
// or pipe (flip (append) (['items']), path);
我不认为它比其他答案中的建议更好或更差,但其中 none 与
一样易于阅读const getItemById = id => path(['items', id]);
最后,如果那些辅助函数没有在其他地方使用,我认为内联它们实际上可以提高可读性:
const getItemsFromObj = pipe (
prop ('items'),
map (id => path(['items', id])),
juxt
)
注意,虽然我没有在这里使用它,但我真的很喜欢customcommander对propOr([], 'items')
对getConnectedItemIds
的建议。它彻底消除了一个潜在的故障点。