使用具有 ramda.js 的高阶函数进行映射
Mapping using higher-order functions with ramda.js
我的代码中有一个模式不断重复出现,看起来应该很常见,但我终究无法弄清楚它的名称或是否有通用的处理方法:map
ing 使用一个函数,该函数接受一个参数,该函数本身是一个函数将 map
ed 元素作为参数的结果。
这是模式本身。我已经将我想要的函数命名为 mapply
(映射应用),但这似乎是错误的名称:
const mapply = (outer, inner) => el => outer(inner(el))(el)
这实际上叫什么?我怎样才能在惯用的 Ramda 中实现它?这似乎是世界上必须有聪明人告诉我如何处理它的事情。
我的用例是做一些基本的准牛顿物理工作,对物体施加力。要计算一些力,您需要一些关于物体的信息——位置、质量、速度等。一个(非常)简化的例子:
const g = Vector.create(0, 1),
gravity = ({ mass }) => Vector.multiply(mass)(g),
applyForce = force => body => {
const { mass } = body,
acceleration = Vector.divide(mass)(force)
return R.merge(body, { acceleration })
}
//...
const gravitated = R.map(mapply(applyForce, gravity))(bodies)
谁能告诉我:这是什么?你会如何 Ramda-fy 呢?我应该注意哪些陷阱、边缘情况和困难?有什么聪明的处理方法?
(我搜索了又搜索——所以,Ramda 的 GitHub 存储库,以及其他一些函数式编程资源。但也许我的 Google-fu 不在需要的地方。抱歉如果我忽略了一些明显的东西。谢谢!)
这是一篇作文。它特别是 compose
(或 pipe
,如果你倒退的话)。
在数学中(考虑单变量微积分),你会有像 fx
或 f(x)
这样的语句,表示有一些函数 f,它变换 x,以及变换应在别处描述...
当你看到 (g º f)(x)
时,你就会发疯。 "G of F"(或许多其他描述)。
(g º f)(x) == g(f(x))
看着眼熟?
const compose = (g, f) => x => g(f(x));
当然,您可以通过将组合函数用作组合函数内部的操作来扩展此范例。
const tripleAddOneAndHalve = compose(halve, compose(add1, triple));
tripleAddOneAndHalve(3); // 5
对于这个的可变参数版本,您可以执行以下两项操作之一,具体取决于您是想更深入地了解函数组合,还是想理顺一点。
// easier for most people to follow
const compose = (...fs) => x =>
fs.reduceRight((x, f) => f(x), x);
// bakes many a noodle
const compose = (...fs) => x =>
fs.reduceRight((f, g) => x => g(f(x)));
但是现在,如果你拿咖喱或部分 map
之类的东西,例如:
const curry = (f, ...initialArgs) => (...additionalArgs) => {
const arity = f.length;
const args = [...initialArgs, ...additionalArgs];
return args.length >= arity ? f(...args) : curry(f, ...args);
};
const map = curry((transform, functor) =>
functor.map(transform));
const reduce = ((reducer, seed, reducible) =>
reducible.reduce(reducer, seed));
const concat = (a, b) => a.concat(b);
const flatMap = curry((transform, arr) =>
arr.map(transform).reduce(concat, []));
你可以做一些漂亮的事情:
const calculateCombinedAge = compose(
reduce((total, age) => total + age, 0),
map(employee => employee.age),
flatMap(team => team.members));
const totalAge = calculateCombinedAge([{
teamName: "A",
members: [{ name: "Bob", age: 32 }, { name: "Sally", age: 20 }],
}, {
teamName: "B",
members: [{ name: "Doug", age: 35 }, { name: "Hannah", age: 41 }],
}]); // 128
非常强大的东西。当然,所有这些在 Ramda 中也可用。
const mapply0 = (outer, inner) => el => outer(inner(el))(el);
const mapply1 = (outer, inner) => R.converge(
R.uncurryN(2, outer),
[
inner,
R.identity,
],
);
const mapply2 = R.useWith(
R.converge,
[
R.uncurry(2),
R.prepend(R.__, [R.identity]),
],
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>
我还没有测试过,但它可能会起作用。
首先是你的函数。
第二个使用 converge 传递 'el' 通过内部函数,然后是身份函数,并将两者传递到外部的非科里化版本。
R.uncurryN(2, outer) 就像这样 outer(inner(el), el),这意味着 converge 可以提供参数。
第三个可能太远了,但无论如何它都很有趣,你调用 converge 时第一个参数是 outer 的未柯里化版本,第二个参数是包含 inner 和身份的数组,useWith 这样做完全删除了函数定义来自解决方案。
我不确定这是否是您要找的,但这是我找到的 3 种写法。
转述自对问题的评论:
mapply
实际上是 chain
:
R.chain(f, g)(x); //=> f(g(x), x)
嗯,主要是。在这种情况下,请注意 x
必须是一个数组。
那么我的解决方案是:
const gravitated = R.map(
R.chain(applyForce, R.compose(R.of, gravity))
)(bodies)
Ramda 的 chain 文档在这种情况下并不是很有帮助:它只是简单地阅读,"chain maps a function over a list and concatenates the results." (ramdajs.com/docs/#chain)
答案隐藏在第二个示例中,其中两个函数被传递给链并部分应用。直到在这里阅读这些答案后我才明白这一点。
(感谢 ftor, bergi, and Scott Sauyet。)
我的代码中有一个模式不断重复出现,看起来应该很常见,但我终究无法弄清楚它的名称或是否有通用的处理方法:map
ing 使用一个函数,该函数接受一个参数,该函数本身是一个函数将 map
ed 元素作为参数的结果。
这是模式本身。我已经将我想要的函数命名为 mapply
(映射应用),但这似乎是错误的名称:
const mapply = (outer, inner) => el => outer(inner(el))(el)
这实际上叫什么?我怎样才能在惯用的 Ramda 中实现它?这似乎是世界上必须有聪明人告诉我如何处理它的事情。
我的用例是做一些基本的准牛顿物理工作,对物体施加力。要计算一些力,您需要一些关于物体的信息——位置、质量、速度等。一个(非常)简化的例子:
const g = Vector.create(0, 1),
gravity = ({ mass }) => Vector.multiply(mass)(g),
applyForce = force => body => {
const { mass } = body,
acceleration = Vector.divide(mass)(force)
return R.merge(body, { acceleration })
}
//...
const gravitated = R.map(mapply(applyForce, gravity))(bodies)
谁能告诉我:这是什么?你会如何 Ramda-fy 呢?我应该注意哪些陷阱、边缘情况和困难?有什么聪明的处理方法?
(我搜索了又搜索——所以,Ramda 的 GitHub 存储库,以及其他一些函数式编程资源。但也许我的 Google-fu 不在需要的地方。抱歉如果我忽略了一些明显的东西。谢谢!)
这是一篇作文。它特别是 compose
(或 pipe
,如果你倒退的话)。
在数学中(考虑单变量微积分),你会有像 fx
或 f(x)
这样的语句,表示有一些函数 f,它变换 x,以及变换应在别处描述...
当你看到 (g º f)(x)
时,你就会发疯。 "G of F"(或许多其他描述)。
(g º f)(x) == g(f(x))
看着眼熟?
const compose = (g, f) => x => g(f(x));
当然,您可以通过将组合函数用作组合函数内部的操作来扩展此范例。
const tripleAddOneAndHalve = compose(halve, compose(add1, triple));
tripleAddOneAndHalve(3); // 5
对于这个的可变参数版本,您可以执行以下两项操作之一,具体取决于您是想更深入地了解函数组合,还是想理顺一点。
// easier for most people to follow
const compose = (...fs) => x =>
fs.reduceRight((x, f) => f(x), x);
// bakes many a noodle
const compose = (...fs) => x =>
fs.reduceRight((f, g) => x => g(f(x)));
但是现在,如果你拿咖喱或部分 map
之类的东西,例如:
const curry = (f, ...initialArgs) => (...additionalArgs) => {
const arity = f.length;
const args = [...initialArgs, ...additionalArgs];
return args.length >= arity ? f(...args) : curry(f, ...args);
};
const map = curry((transform, functor) =>
functor.map(transform));
const reduce = ((reducer, seed, reducible) =>
reducible.reduce(reducer, seed));
const concat = (a, b) => a.concat(b);
const flatMap = curry((transform, arr) =>
arr.map(transform).reduce(concat, []));
你可以做一些漂亮的事情:
const calculateCombinedAge = compose(
reduce((total, age) => total + age, 0),
map(employee => employee.age),
flatMap(team => team.members));
const totalAge = calculateCombinedAge([{
teamName: "A",
members: [{ name: "Bob", age: 32 }, { name: "Sally", age: 20 }],
}, {
teamName: "B",
members: [{ name: "Doug", age: 35 }, { name: "Hannah", age: 41 }],
}]); // 128
非常强大的东西。当然,所有这些在 Ramda 中也可用。
const mapply0 = (outer, inner) => el => outer(inner(el))(el);
const mapply1 = (outer, inner) => R.converge(
R.uncurryN(2, outer),
[
inner,
R.identity,
],
);
const mapply2 = R.useWith(
R.converge,
[
R.uncurry(2),
R.prepend(R.__, [R.identity]),
],
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>
我还没有测试过,但它可能会起作用。
首先是你的函数。
第二个使用 converge 传递 'el' 通过内部函数,然后是身份函数,并将两者传递到外部的非科里化版本。
R.uncurryN(2, outer) 就像这样 outer(inner(el), el),这意味着 converge 可以提供参数。
第三个可能太远了,但无论如何它都很有趣,你调用 converge 时第一个参数是 outer 的未柯里化版本,第二个参数是包含 inner 和身份的数组,useWith 这样做完全删除了函数定义来自解决方案。
我不确定这是否是您要找的,但这是我找到的 3 种写法。
转述自对问题的评论:
mapply
实际上是 chain
:
R.chain(f, g)(x); //=> f(g(x), x)
嗯,主要是。在这种情况下,请注意 x
必须是一个数组。
那么我的解决方案是:
const gravitated = R.map(
R.chain(applyForce, R.compose(R.of, gravity))
)(bodies)
Ramda 的 chain 文档在这种情况下并不是很有帮助:它只是简单地阅读,"chain maps a function over a list and concatenates the results." (ramdajs.com/docs/#chain)
答案隐藏在第二个示例中,其中两个函数被传递给链并部分应用。直到在这里阅读这些答案后我才明白这一点。
(感谢 ftor, bergi, and Scott Sauyet。)