Javascript 与玻璃钢作战

Javascript 与玻璃钢作战,javascript,reactive-programming,frp,bacon.js,Javascript,Reactive Programming,Frp,Bacon.js,我读过关于玻璃钢的书,非常激动。 它看起来很棒,因此您可以编写更多的高级代码,并且所有内容都更易于组合,等等 然后我试着用几百个sloc从普通js到培根重写我自己的小游戏 我发现,我并没有编写高级逻辑代码,而是用Bacon.js及其对原则的坚持击败了它 我遇到了一些主要干扰干净代码的头痛问题 .take(1) 我应该创造丑陋的建筑,而不是获得价值 循环依赖 有时,它们应该符合逻辑。但是在FRP中实现它是可怕的 活动状态 即使是bacon.js的创建者也有这样的想法 这里的示例是代码的和平,以演

我读过关于玻璃钢的书,非常激动。 它看起来很棒,因此您可以编写更多的高级代码,并且所有内容都更易于组合,等等

然后我试着用几百个sloc从普通js到培根重写我自己的小游戏

我发现,我并没有编写高级逻辑代码,而是用Bacon.js及其对原则的坚持击败了它

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

  • .take(1)
  • 我应该创造丑陋的建筑,而不是获得价值

  • 循环依赖
  • 有时,它们应该符合逻辑。但是在FRP中实现它是可怕的

  • 活动状态
  • 即使是bacon.js的创建者也有这样的想法


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

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

    用bacon.js实现

    我想展示的是:

  • me.positions
    有其自身的依赖性
  • 这段代码不容易理解。这是当务之急。而且看起来更容易理解。我花在培根实现上的时间多得多。结果,我不确定它是否会像预期的那样工作

  • 我的问题: 也许我错过了一些基本的东西。也许我的实现不是FRP风格的

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


    还是这个众所周知的问题,我应该选择万恶中之最?因此,FRP的问题就像前面描述的,或者OOP的问题。

    我在尝试用Bacon和RxJs编写游戏时也有过类似的经历。有自我依赖性的事情(比如球员的位置)很难用“纯FRP”的方式处理

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

    另一种方法是像Elm的人一样:将整个游戏状态建模为单个属性(或在Elm中称为信号),并基于该完整状态计算下一个状态

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


    因此,Bacon.js不是一颗灵丹妙药。我建议你找出自己,在哪里使用玻璃钢,在哪里不使用

    我有时也有类似的填充物。对我来说,使用FRP编程的经验主要是解决难题。有些是容易的,有些不是。而那些我觉得容易的可能比我的同事们更难,反之亦然。我不喜欢玻璃钢

    别误会,我喜欢解谜题,这很有趣!但我认为付费工作的编程应该更。。。没趣的更可预测。代码应该尽可能简单,甚至是原始的

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


    另外,请注意您的代码,我认为这将更像FRP'ish(未经测试的草稿):


    我认为在Elm中,你不必将玩家的动作建立在“完全状态”的基础上,你也可以创建小型的独立“模块”。当然,正如在任何纯语言中一样,您必须将它们组合到一个全局的完整程序中。当然,在Elm中,您不能有循环的DEP,这会将您推到这个“作为信号的世界”模型中。我并不是说这是不好的,只是指出它可能是以全功能风格编写游戏时唯一有效的模型。比如说,他们支持这一点,因为只有一个单一的世界信号依赖于自身(pong示例中的
    gameState
    )。但是,是的,你可以有循环依赖,只要在任何地方使用
    Signal.foldp
    。好吧,有点循环,因为你的信号可以依赖于它自己的过去值。但是信号A和B不能相互依赖。这在游戏中经常发生。啊,你指的就是这个。虽然真正的循环依赖关系根本无法解决,但您的意思是
    a
    确实依赖于前面的
    a
    和前面的
    B
    就像
    B
    依赖于前面的两个值一样。对于这些,你需要一个共同的“世界信号”,它包括
    a
    B
    事实上。我认为简单是一回事,“原始”或“无聊”是另一回事。。。FP声称自己能带来更容易理解的代码,因此应该是更直观的代码,但正如我们所看到的,尤其是在FRP中,这并不总是正确的。那就没有银弹了。
    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);
    
    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;
            }
        });