7

我最近在阅读有关游戏引擎设计的内容,最终偶然发现:什么是(功能性)反应式编程?

我想知道如何准确地实施第二高评分答案中给出的示例。在 C++ 中,将指针传递给存储鼠标坐标的值并返回它的值而不是 int 就很容易了。好吧,我们不能在 C# 中真正做到这一点,所以这是我们的第一个问题。我们是否需要调用一些“更新”函数来保持所有值都是最新的?

其次,如何处理语法?赋值是直截了当的。但是,像“每次我要求它时获取鼠标位置和 14 分钟”之类的事情稍微多一点..复杂..

最后,我想知道当你直接引用它时,如何让 C# 中的任何对象返回一个值。例如

int test = 1;

test 会返回 1。所以我可以做 1 + test which would = 2 之类的事情

但如果我有一个实例

public class ReactiveInt {
     int myValue
}

在尝试将 int 添加在一起时,我不能只做上面所做的事情。

对不起,我想这么广泛的问题。如果可以给出一个简单的示例来演示与该答案中讨论的功能相似的功能,我认为我的所有问题都会得到解答。

4

3 回答 3

9

问题

问题 1

在尝试将 int 添加在一起时,我不能只做上面所做的事情。

嗯,这就是重点。在反应式编程中,您不想强制地将两个数字相加,而是希望根据其他数字定义新数字。因此,即使或发生变化, 也总是等于 :c = a + bc对于是反应性的。a + babcab

var a = new BehaviorSubject(3);
var b = new BehaviorSubject(1);
var c = Rx.Observable.combineLatest(a, b, function(vals) {
    return vals[0] + vals[1];
});

问题2

我想知道如何准确地实施第二高评分答案中给出的示例。

haskell 中最简单的答案列表和高阶函数。

回答你不希望函数式反应式编程与你在命令式编程中学到的一切背道而驰,如果你想做纯函数式反应式编程,你将不得不重新学习如何做事。如果你不这样做,你最终会制作各种依赖跟踪库,比如KnockoutJS ,如果你使用 FRP 开始,你可以用RxJS-Splash之类的东西在几百行中做同样的事情. (注意 Splash 如何基于 Rx,这是可重用的代码,而 Knockout 是纯粹的实现特定代码)。

FRP 有事件时间的概念,而依赖跟踪只有变化的概念。功能响应式代码与命令式代码一样长。它不是“建立在命令式代码之上”。(是的,它仍然编译为汇编......不是重点),它在概念上根本不同。

例子

使用 Microsoft 的 JavaScript 响应式扩展 ( RxJS )

请记住,Rx 现在可以在很多语言中使用,包括原生C++

示例的直接端口

var moves = $(document).onAsObservable('mousemove')
    .map(function(e) {
        return {
            x: e.pageX,
            y: e.pageY
        };
    });

var xs = moves.map(function(move) { return move.x; });
var ys = moves.map(function(move) { return move.y; });

var minXs = xs.map(function(x) { return x - 16; });
var minYs = ys.map(function(y) { return y - 16; });
var maxYs = xs.map(function(x) { return x + 16; });
var maxYs = ys.map(function(y) { return y + 16; });

var boundingRect = Rx.Observable.combineLatest(minXs, minYs, maxXs, maxYs)
    .map(function(vals) {
        var minX = vals[0];
        var minY = vals[1];
        var maxX = vals[2];
        var maxY = vals[3];

        return new Rectangle(minX, minY, maxX, maxY);
    });

简化端口

由于矩形仅根据一个相关值(或事件)定义,因此您可以将其简化为以下内容:

var boundingRect = $(document).onAsObservable('mousemove')
    .map(function(e) {
        var x = e.pageX;
        var y = e.pageY;
        return new Rectangle(x - 16, y - 16, x + 16, y + 16);
        });

使用它

此时,您可以使用它来组成其他可观察的序列(随时间变化的值)。

var area = boundingRect.map(function(rect) {
    return rect.getSize();
    });

或者直接订阅。

boundingRect.subscribe(function (rect) {
    // perform some action with the rect each time it changes.
    console.log(rect);
    });

但这只是在它改变的时候!

如果我们一订阅就想要最新的值,而不是等待矩形再次改变怎么办?嗯,这就是BehaviorSubjects 进来的地方。

var rects = new Rx.BehaviorSubject(new Rectangle(-1, -1, 1, 1));

rects.subscribe(function(rect) {
        // this would happen twice in this example.
        // Once for the initial value (above), and once when it is changed later (below).
        console.log(rect); 
    });

rects.onNext(new Rectangle(-1, -1, 1, 1));

但这不是原始的 observable,而且它不是很实用......

以下是如何使用一些内置功能对原始 observable 执行相同的操作,以将 Observable 更改为类似于 BehaviorSubject 的行为...

var rectsAndDefault = rects.startWith(new Rectangle()); // just give it an initial value
rectsAndDefault.subscribe(function(rect) {
    console.log(rect); // happens once for the "startWith" rectangle, and then again for all subsequent changes
    })

同样,FRP 是不同的。这是很好的不同,但这是一项艰巨的学习任务。当它开始让你大吃一惊时,你就会知道你已经开始明白了。

于 2013-03-12T19:44:19.117 回答
1

看看微软的响应式扩展(Rx)

于 2012-04-20T13:09:46.120 回答
0

使用 Lambdas 和 Actions 作为事件处理程序,您可以在没有指针的情况下摆脱困境。

int Offset = 16;
int Size = Offset * 2;
Action<MouseEventArgs> MouseEventArgsHandler = (args) => DrawRectangle(args.x - Offset, args.y - Offset, Size, Size);
于 2012-02-09T21:22:00.893 回答