简体   繁体   English

我应该如何在FRP(Rx.Net)中建模循环依赖?

[英]How should I model circular dependencies in FRP (Rx.Net)?

I am trying to learn more about functional-reactive programming by using Rx.Net to implement Tic-Tac-Toe. 我试图通过使用Rx.Net来实现Tic-Tac-Toe来学习更多关于功能反应式编程的知识。 The problem I am having is that there seems to be a circular dependency in my game-logic. 我遇到的问题是我的游戏逻辑似乎存在循环依赖。

The commands stream ( PlaceToken , ResetGame , etc.) is generated from user-input streams. commands流( PlaceTokenResetGame等)是从用户输入流生成的。

The current state of the game ( boardStates ) is derived by applying commands to the previous state, starting with the initial state: 游戏的当前状态( boardStates )是通过将commands应用于先前状态而得到的,从初始状态开始:

var initialBoardState = new BoardState();

var boardStates = commands
    .Scan(initialBoardState, (boardState, command) => command.Apply(boardState))
    .DistinctUntilChanged();

However, the commands stream should depend on the boardStates stream. 但是, commands流应该取决于boardStates流。 This is because the valid set of commands changes with the current state. 这是因为有效的命令集随当前状态而变化。

For example, the PlaceToken command should only be issued when the user clicks on an empty tile, but the set of empty tiles is defined by the current state! 例如, PlaceToken命令只应在用户单击空PlaceToken时发出,但空PlaceToken贴的集合由当前状态定义!

So to summarise, I have two streams which seem to depend on each-other. 总而言之,我有两个似乎依赖于彼此的流。 How should I work around this in functional-reactive programming? 我应该如何在功能反应式编程中解决这个问题?

While @LeeCampbell's solution does work, it requires making your core model classes mutable. 虽然@ LeeCampbell的解决方案确实有效,但它需要使您的核心模型类可变。 Instead, I found it best to copy the approach taken by cycle.js. 相反,我发现最好复制cycle.js采用的方法。 You can see their explanation here . 你可以在这里看到他们的解释。

The problem is that we have a cycle. 问题是我们有一个循环。 The stream of actions depends on the stream of board states, which in turn depends on the stream of actions: 动作流取决于董事会状态流,而这又取决于行动流:

boardStream = f(actionStream)
actionStream = g(boardStream)

The cycle.js solution is to use a proxy stream to wire everything together: cycle.js解决方案是使用代理流将所有内容连接在一起:

// Create a proxy
proxyActionStream = new Stream()

// Create our mutually dependent streams using the proxy
boardStream = f(proxyActionStream)
actionStream = g(boardStream)

// Feed actionStream back into proxyActionStream
actionStream.Subscribe(x => proxyActionStream.OnNext(x))

In Rx.Net land, the proxy stream should be a ReplaySubject . 在Rx.Net中,代理流应该是ReplaySubject

The only place you have to be careful is with run-away feedback loops: if your streams never stabilize then they hit an infinite loop! 唯一需要注意的地方是失控反馈循环:如果你的流不会稳定,那么它们会无限循环! It is helpful to think of the streams as a mutual recursion. 将流视为相互递归是有帮助的。

Not everything needs to be an event. 并非一切都需要成为一个事件。 Remember Rx/Callbacks are a way of allowing something that you depend on, to call you back (without taking a dependency on you). 记住Rx / Callbacks是一种允许你依赖的东西,给你回电话(不依赖你)。

As a rule of thumb 根据经验

  1. Circular dependencies indicate a design flaw 循环依赖表示设计缺陷
  2. Send Commands, Receive Events 发送命令,接收事件

2) can also be translated to 2)也可以翻译成

Just call methods on your dependencies to change their state, but subscribe to their events to see their changes 只需调用依赖项上的方法来更改其状态,但订阅其事件以查看其更改

So instead of thinking of Commands as a stream, you should think of user actions as a stream that when listened to may create a command. 因此,不应将Commands视为流,而应将用户操作视为流,当收听时可能会创建命令。

So the BoardState might look something like this 所以BoardState可能看起来像这样

public class BoardState
{
    public void PlaceToken(PlaceTokenCommand placeToken)
    {
        //Process, then raise event
    }

    public void Reset()
    {
        //Process, then raise event
    }

    public IObservable<?> StateUpdates()
    {

    }
}

and the ViewModel(?) code might look something like this 并且ViewModel(?)代码可能看起来像这样

public class TicTacToeViewModel
{
    private readonly BoardState _board;
    public TicTacToeViewModel()
    {
        _board = new BoardState();
        MoveTokenCommand = new DelegateCommand(MoveToken, CanMoveToken);
        ResetBoardCommand = new DelegateCommand(_board.Reset);

        board.StateUpdates(state => UpdatePresentation(state));
    }

    public DelegateCommand MoveTokenCommand { get; private set;}
    public DelegateCommand ResetBoardCommand { get; private set;}


    private void MoveToken()
    {
        var token = CurrentToken;
        var location = ActiveLocation;
        var cmd = new PlaceTokenCommand(token, location);
        _board.PlaceToken(cmd);
    }

    private bool CanMoveToken()
    {
        //?
    }
}

But as @Enigmativity, requests in the comments, without a MCVE it is very hard to offer sensible help. 但是作为@Enigmativity,评论中的请求,没有MCVE,很难提供合理的帮助。

Last note, While Rx is Functional and it is Reactive, the zealots would object to Rx being considered FRP (see Conal, Behaviours etc). 最后注意,虽然Rx是功能性的并且它是反应性的,但狂热者会反对将Rx视为FRP (参见Conal,Behaviors等)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM