与FRP战斗

时间:2015-04-10 14:25:08

标签: javascript reactive-programming frp bacon.js

我读过FRP,非常兴奋。 它看起来很棒,所以你可以编写更多高级代码,而且所有内容都更具组合性等等。

然后我试图用普通的js到Bacon重写我自己的小游戏。

我发现不是编写高级逻辑代码,而是使用Bacon.js并遵守原则。

我遇到了一些令人头痛的问题,主要是干扰代码干净

  1. .take(1)
  2. 而不是获得价值,我应该创造丑陋的结构。

    1. 循环依赖
    2. 有时它们应该是逻辑的。但在FRP中实施它是可怕的

      1. 活跃状态
      2. 即使是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是必要的实施。它看起来更容易理解。我花了很多时间用培根实施。结果我不确定它会按预期工作。

        3. 我的问题:

          可能我会错过一些基本的东西。也许我的实施在FRP风格中并非如此?

          也许这段代码看起来不错,它只是不熟悉新的编码风格?

          或者这个众所周知的问题,我应该选择最好的邪恶?如此描述的FRP的麻烦,或OOP的麻烦。

2 个答案:

答案 0 :(得分:4)

我尝试用Bacon和RxJs编写游戏时遇到了类似的经历。具有自我依赖性的东西(如玩家的位置)很难以“纯粹的FRP”方式处理。

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

另一种方法是像Elm那样做:将完整的游戏状态建模为单个属性(或在Elm中调用的信号),并根据完整状态计算下一个状态。

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

所以,Bacon.js不是银弹。我建议你找出自己,在哪里申请FRP,哪里不用!

答案 1 :(得分:1)

我有时会有类似的填充。对我来说,用FRP编程的经验主要是解决谜题。其中一些很容易,一些不容易。那些我觉得容易的人可能会比同事更难,反之亦然。关于FRP,我不喜欢这样。

不要误会我的意思,我喜欢解决难题,这很有趣!但我认为付费工作的编程应该更加......无聊。更可预测。代码应该尽可能简单,甚至是原始代码。

但当然全球可变状态也不是我们应该采取的方式。我认为我们应该找到一种让FRP更无聊的方法:)


同样是对您的代码的评论,我认为这将是更多的FRP' ish(未经测试的草案):

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