与玻璃钢战斗

Fighting with FRP

我读过有关 FRP 的文章,感到非常兴奋。 它看起来很棒,所以你可以写更多的高级代码,一切都更可组合,等等

然后我尝试用几百个 sloc 从普通 js 到 Bacon 重写我自己的小游戏。

而且我发现,与其编写仅包含高级逻辑的代码,我实际上还在击败 Bacon.js 及其对原则的遵守。

我 运行 有点头疼,主要是干扰干净的代码

  1. .take(1)

与其获取价值,不如创建丑陋的结构。

  1. 循环依赖

有时他们应该符合逻辑。但是在 FRP 中实现它是可怕的

  1. 活动状态

甚至 bacon.js 的创建者也有 troubles


这里的示例是演示问题的代码和平:

任务是不允许两个玩家呆在同一个地方

通过 bacon.js

实现

http://jsbin.com/zopiyarugu/2/edit?js,console

function add(a) {return function(b){return a + b}}
function nEq(a) {return function(b){return a !== b}}
function eq(a) {return function(b){return a === b}}
function always(val) {return function(){return val}}
function id(a){return a}

var Player = function(players, movement, initPos){
    var me = {};
    me.position = movement
        .flatMap(function(val){
            return me.position
                .take(1)
                .map(add(val))
        })
        .flatMap(function(posFuture){
            var otherPlayerPositions = players
                .filter(nEq(me))
                .map(function(player){return player.position.take(1)})
            return Bacon
                .combineAsArray(otherPlayerPositions)
                .map(function(positions){
                    return !positions.some(eq(posFuture));
                })
                .filter(id)
                .map(always(posFuture))
        })
        .log('player:' + initPos)
        .toProperty(initPos);
    return me;
}

var moveA = new Bacon.Bus();
var moveB = new Bacon.Bus();

var players = [];
players.push(new Player(players, moveA, 0));
players.push(new Player(players, moveB, 10));

moveA.push(4);
moveB.push(-4);
moveA.push(1);
moveB.push(-1);
moveB.push(-1);
moveB.push(-1);
moveA.push(1);
moveA.push(-1);
moveB.push(-1);

我要演示的是:

  1. me.positions 对自己有依赖性
  2. 理解这段代码并不容易。 Here 是命令式的实现。而且看起来更容易理解。我花了更多时间在培根实施上。结果我不确定它是否会按预期工作。

我的问题:

可能我错过了一些基本的东西。也许我的实现不是 FRP 风格?

也许这段代码看起来还不错,只是不习惯新的编码风格?

还是这个众所周知的问题,我应该选择best of all evil?所以像描述的那样 FRP 的问题,或者 OOP 的问题。

我在尝试使用 Bacon 和 RxJs 编写游戏时也有过类似的经历。具有独立性的事物(例如玩家的位置)很难以 "pure FRP" 的方式处理。

例如,在我早期的 Worzone 游戏中,我包含了一个可变 targets 对象,可以查询玩家和怪物的位置。

另一种方法是像 Elm 的家伙那样做:将完整的游戏状态建模为单个 属性(或 E​​lm 中称为 Signal)并根据该完整状态计算下一个状态。

到目前为止我的结论是 FRP 不太适合游戏编程,至少在 "pure" 方面是这样。毕竟,对于某些事情,可变状态可能是更可组合的方法。在一些游戏项目中,例如 Hello World Open 赛车,我使用了可变状态,例如用于存储状态的 DOM 和用于传递事件的 EventStreams。

所以,Bacon.js 不是灵丹妙药。我建议你自己弄清楚,哪里可以应用FRP,哪里不能应用!

我有时也有类似的填充物。对我来说,用 FRP 编程的经历主要是解谜。其中有些很容易,有些则不然。而那些我觉得容易的可能对我的同事来说更难,反之亦然。我不喜欢 FRP。

不要误会我的意思,我喜欢解谜,这很有趣!但我认为在有偿工作中编程应该更……无聊。更可预测。并且代码应该尽可能简单,甚至是原始的。

当然,全局可变状态也不是我们应该走的路。我想我们应该想办法让 FRP 更无聊:)


还有关于您的代码的评论,我认为这将更像 FRP(未经测试的草稿):

var otherPlayerPositions = players
    .filter(nEq(me))
    .map(function(player){return player.position});

otherPlayerPositions = Bacon.combineAsArray(otherPlayerPositions);

me.position = otherPlayerPositions
    .sampledBy(movement, function(otherPositions, move) {
        return {otherPositions: otherPositions, move: move};
    })
    .scan(initPos, function(myCurPosition, restArgs) {
        var myNextPosition = myCurPosition + restArgs.move;
        if (!restArgs.otherPositions.some(eq(myNextPosition))) {
            return myNextPosition;
        } else {
            return myCurPosition;
        }
    });