Dus*_*etz 8 javascript functional-programming pointfree
玩无点风格的 javascript以获得乐趣.
假设我正在编写视频游戏"暗黑破坏神",我正在使用像这样的复杂嵌套类型对敌人进行建模,但更深入,更复杂:
{ name: "badguy1", stats: { health: 10: strength: 42 }, pos: {x: 100, y: 101 } }
Run Code Online (Sandbox Code Playgroud)
所以我列出了我所有的敌人.我想对某个半径范围内的所有敌人造成伤害
function isInRange(radius, point) { return point.x^2 + point.y^2 >= radius^2; }
function fireDamage(health) { return health - 10; }
var newEnemies = enemies.filter(isInRange).map(fireDamage);
Run Code Online (Sandbox Code Playgroud)
这当然不是类型检查 - 我的组合器采用原语,所以我需要映射和过滤"另一个级别".我不想模糊过滤器/地图业务逻辑管道.我知道镜头可以帮助我,但我可以说我在浏览器中,因为这对于可变结构来说当然是微不足道的.我该怎么做?
您的问题是关于如何在Javascript中使用镜头?如果是这样,我可能会提供帮助.你检查过Ramda.js库吗?编写功能JS是一种很棒的方式.让我们从看你的敌人模型开始:
/* -- data model -- */
let enemyModel = {
name: "badguy1",
stats: {
health: 10,
strength: 42
},
pos: {
x: 100,
y: 101
}
};
Run Code Online (Sandbox Code Playgroud)
镜头:为了构建镜头,你需要一个getter方法和一个针对你特定对象的setter方法 - 在你的情况下是"敌人".以下是您可以手动构建这些内容的方法.
方法1:创建自己的getter和setter
const getHealth = path(['stats', 'health']);
const setHealth = assocPath(['stats', 'health']);
const healthLens = lens(getHealth, setHealth);
Run Code Online (Sandbox Code Playgroud)
方法2: Ramda用于物体的便利镜头
const healthLens = lensPath(['stats', 'health']);
Run Code Online (Sandbox Code Playgroud)
一旦你创造了镜头,就可以使用它了.Ramda提供了使用镜头3个功能:view(..),set(..),和over(..).
view(healthLens)(enemyModel); // 10
set(healthLens, 15)(enemyModel); // changes health from 10 to 15
over(healthLens, fireDamage)(enemyModel); // reduces enemyModel's health property by 10
Run Code Online (Sandbox Code Playgroud)
既然你将这个fireDamage(..)功能应用于敌人的健康,你就会想要使用它over(..).此外,由于你的位置坐标嵌套在敌人模型中,你也会想要使用镜头来访问它们.让我们创造一个和重构,isInRange(..)而我们在它.
作为参考,这里是原点fn:
// NOTE: not sure if this works as you intended it to...
function isInRange(radius, point) {
return point.x^2 + point.y^2 >= radius^2; // maybe try Math.pow(..)
}
Run Code Online (Sandbox Code Playgroud)
这是一种功能性方法:
/* -- helper functions -- */
const square = x => x * x;
const gteRadSquared = radius => flip(gte)(square(radius));
let sumPointSquared = point => converge(
add,
[compose(square, prop('x')),
compose(square, prop('y'))]
)(point);
sumPointSquared = curry(sumPointSquared); // allows for "partial application" of fn arguments
/* -- refactored fn -- */
let isInRange = (radius, point) => compose(
gteRadSquared(radius),
sumPointSquared
)(point);
isInRange = curry(isInRange);
Run Code Online (Sandbox Code Playgroud)
这是处理敌人模型集合时的样子:
/* -- lenses -- */
const xLens = lensPath(['pos', 'x']);
const yLens = lensPath(['pos', 'y']);
const ptLens = lens(prop('pos'), assoc('pos'));
// since idk where 'radius' is coming from I'll hard-code it
let radius = 12;
const filterInRange = rad => filter(
over(ptLens, isInRange(rad)) // using 'ptLens' bc isInRange(..) takes 'radius' and a 'point'
);
const mapFireDamage = map(
over(healthLens, fireDamage) // using 'healthLens' bc fireDamage(..) takes 'health'
);
let newEnemies = compose(
mapFireDamage,
filterInRange(radius)
)(enemies);
Run Code Online (Sandbox Code Playgroud)
我希望这有助于说明镜头的用途.虽然有很多辅助函数,但我认为最后一段代码是超级语义的!
最后,我只是使用Ramda中的这些函数充斥我的范围,使这个例子更具可读性.我正在使用ES6解构来实现这一目标.这是如何做:
const {
add,
assocPath,
compose,
converge,
curry,
filter,
flip,
gte,
lens,
lensPath,
map,
over,
set,
path,
prop,
view
} = R;
// code goes below...
Run Code Online (Sandbox Code Playgroud)
在jsBin中尝试一下!他们提供Ramda支持.
阅读我关于镜片的文章.它完全按照您的措辞回答您的问题.说真的,我甚至不开玩笑.这是我帖子中的代码片段:
fireBreath :: Point -> StateT Game IO ()
fireBreath target = do
lift $ putStrLn "*rawr*"
units.traversed.(around target 1.0).health -= 3
Run Code Online (Sandbox Code Playgroud)