[英]Mapping using higher-order functions with ramda.js
我在我的代码,一再重复发生,似乎像它应该是相当常见的一种模式,但我不能为我的生活弄清楚它叫什么或是否有处理它常用方法: map
使用函数ING是接受一个参数,该参数本身就是将map
ed元素作为参数的函数的结果。
这是模式本身。 我已将要命名的函数命名为mapply
(map-apply),但这似乎是错误的名称:
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? 我应该注意哪些陷阱,边缘情况,困难? 有哪些明智的处理方式?
(我一直在搜索和搜索-SO,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,external)的工作方式与此外部(inner(el),el)相同,这意味着收敛可以提供参数。
第三个参数可能太远了,但还是很有趣,您正在调用converge,其中第一个参数是外部的未固化版本,第二个参数是包含内部和标识的数组,useWith这样做可以从解决方案中完全删除函数定义。
我不确定这是否是您要寻找的东西,但这是我发现的三种书写方式。
从对问题的评论中解释:
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的链文档在这种情况下并不是很有帮助:它读起来很简单,“链将函数映射到列表上并连接结果”。 (ramdajs.com/docs/#chain)
答案隐藏在第二个示例中,其中两个函数传递给chain并部分应用。 在阅读完这些答案后,我才能看到。
(感谢ftor , bergi和Scott Sauyet 。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.