这是javascript功能镜头的一个位置吗?

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)

这当然不是类型检查 - 我的组合器采用原语,所以我需要映射和过滤"另一个级别".我不想模糊过滤器/地图业务逻辑管道.我知道镜头可以帮助我,可以说我在浏览器中,因为这对于可变结构来说当然是微不足道的.我该怎么做?

wpc*_*rro 7

您的问题是关于如何在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支持.


Gab*_*lez 6

阅读我关于镜片的文章.它完全按照您的措辞回答您的问题.说真的,我甚至不开玩笑.这是我帖子中的代码片段:

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)