【问题标题】:is this a spot for functional lenses in javascript?这是javascript中功能镜头的地方吗?
【发布时间】:2013-09-04 03:29:57
【问题描述】:

为了好玩而玩弄point-free style javascript。

假设我正在编写视频游戏《暗黑破坏神》,并且我正在使用像这样的复杂嵌套类型来建模敌人,但更深更复杂:

{ name: "badguy1", stats: { health: 10: strength: 42 }, pos: {x: 100, y: 101 } }

所以我有一个我所有敌人的清单。我想对一定范围内的所有敌人造成伤害

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);

这当然不是类型检查——我的组合器采用原语,所以我需要映射和过滤“下一个级别”。我不想掩盖过滤器/映射业务逻辑管道。 I know lenses can help me 但可以说我在浏览器中,因为这对于可变结构来说当然是微不足道的。我该怎么做?

【问题讨论】:

    标签: javascript functional-programming pointfree


    【解决方案1】:

    您的问题是关于如何在 Javascript 中使用镜头?如果是这样,我也许可以提供帮助。您查看过Ramda.js library 吗?这是编写函数式 JS 的绝佳方式。让我们先看看你的敌人模型:

    /* -- data model -- */
    let enemyModel = {
      name: "badguy1",
      stats: {
        health: 10,
        strength: 42
      },
      pos: {
        x: 100,
        y: 101
      }
    };
    

    镜头:为了构建镜头,您需要针对特定​​对象(在您的情况下为“敌人”)使用 getter 方法和 setter 方法。以下是您可以手动构建它们的方法。

    方法一:创建自己的getter和setter

    const getHealth = path(['stats', 'health']);
    const setHealth = assocPath(['stats', 'health']);
    const healthLens = lens(getHealth, setHealth);
    

    方法 2: Ramda 的权宜之计的 Objects 镜头

    const healthLens = lensPath(['stats', 'health']);
    

    创建镜头后,就可以使用它了。 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
    

    由于您将fireDamage(..) 函数应用于敌人的生命值,因此您需要使用over(..)。此外,由于您的位置坐标嵌套在enemyModel 中,因此您还需要使用镜头来访问它们。让我们创建一个并重构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(..)
    }
    

    这是一个函数式方法:

    /* -- 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);
    

    这是处理一组敌人模型时的样子:

    /* -- 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);
    

    我希望这有助于说明镜头的用途。虽然有很多辅助函数,但我认为最后一段代码是超语义的!

    最后,我只是用 Ramda 的这些函数来扩展我的范围,以使这个示例更具可读性。我正在使用 ES6 解构来实现这一点。方法如下:

    const {
      add,
      assocPath,
      compose,
      converge,
      curry,
      filter,
      flip,
      gte,
      lens,
      lensPath,
      map,
      over,
      set,
      path,
      prop,
      view
    } = R;
    
    // code goes below...
    

    在 jsBin 中试试吧!他们提供 Ramda 支持。

    【讨论】:

      【解决方案2】:

      阅读my article on lenses。它完全按照您的措辞回答了您的问题。说真的,我什至不是在开玩笑。这是我帖子中的代码 sn-p:

      fireBreath :: Point -> StateT Game IO ()
      fireBreath target = do
          lift $ putStrLn "*rawr*"
          units.traversed.(around target 1.0).health -= 3
      

      【讨论】:

      • @DustinGetz 你的意思是要把它翻译成 Javascript 吗?
      • @DustinGetz 您是想完全按照 lens 库的风格来做(即函数的函数?)还是只是制作一流的 getter 和 setter?
      • 我的价值观嵌套更深,但不是图表。说深度为 4。我认为函数的函数是需要的。
      猜你喜欢
      • 2014-01-08
      • 2017-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-30
      • 1970-01-01
      相关资源
      最近更新 更多