反应式编程中流之间的循环依赖关系

2023-12-19

在涉足反应式编程时,我经常遇到两个流相互依赖的情况。解决这些案例的惯用方法是什么?

一个最小的例子:有按钮 A 和 B,都显示一个值。单击 A 必须将 A 的值增加 B。单击 B 必须将 B 的值设置为 A。

我能想到的第一个解决方案(F# 中的示例,但欢迎使用任何语言的答案):

let solution1 buttonA buttonB =
    let mutable lastA = 0
    let mutable lastB = 1
    let a = new Subject<_> ()
    let b = new Subject<_> ()
    (OnClick buttonA).Subscribe(fun _ -> lastA <- lastA + lastB; a.OnNext lastA) 
    (OnClick buttonB).Subscribe(fun _ -> lastB <- lastA; b.OnNext lastB)
    a.Subscribe(SetText buttonA)
    b.Subscribe(SetText buttonA)
    a.OnNext 0
    b.OnNext 1

该解决方案使用可变状态和主题,它的可读性不太好,而且看起来不惯用。

我尝试的第二个解决方案涉及创建一个将两个依赖流链接在一起的方法:

let dependency (aGivenB: IObservable<_> -> IObservable<_>) (bGivenA: IObservable<_> -> IObservable<_>) =
    let bProxy = new ReplaySubject<_> () 
    let a = aGivenB bProxy
    let b = bGivenA a
    b.Subscribe(bProxy.OnNext)
    a, b

let solution2 buttonA buttonB =
    let aGivenB b =
        Observable.WithLatestFrom(OnClick buttonA, b, fun click bValue -> bValue)
                  .Scan(fun acc x -> acc + x)
                  .StartWith(0)
    let bGivenA a =
        Observable.Sample(a, OnClick buttonB)
                  .StartWith(1)
    let a, b = dependency aGivenB bGivenA
    a.Subscribe(SetText buttonA)
    b.Subscribe(SetText buttonB)

这看起来好一点,但是由于不存在类似的方法dependency在反应式库中,我相信存在更惯用的解决方案。使用第二种方法也很容易引入无限递归。

在反应式编程中,处理涉及流之间循环依赖关系的问题(例如上面的示例)的推荐方法是什么?


EDIT:

这是一个 F# 解决方案:

type DU = 
    | A 
    | B 

type State = { AValue : int; BValue : int }

let solution2 (aObservable:IObservable<_>, bObservable:IObservable<_>) = 

    let union = aObservable.Select(fun _ -> A).Merge(bObservable.Select(fun _ -> B))

    let result = union.Scan({AValue = 0; BValue = 1}, fun state du -> match du with
        | A -> { state with AValue = state.AValue + state.BValue }
        | B -> { state with BValue = state.AValue }
    )

    result

得益于内置的可区分联合和记录,F# 实际上是一种很棒的语言。这是用 C# 编写的答案,带有自定义的受歧视联盟;我的 F# 已经生锈了。

诀窍是使用可区分联合将两个可观察量转变为一个可观察量。所以基本上将 a 和 b 联合成一个受歧视联合的可观察值:

a : *---*---*---**
b : -*-*--*---*---
du: ab-ba-b-a-b-aa

完成后,您就可以对该项目是“A”推送还是“B”推送做出反应。

只是为了确认,我认为没有办法明确set嵌入在 Button/Button 中的值。如果有的话,这些变化应该被建模为可观察的,并且也被纳入受歧视的联盟中。

var a = new Subject<Unit>();
var b = new Subject<Unit>();
var observable = a.DiscriminatedUnion(b)
    .Scan(new State(0, 1), (state, du) => du.Unify(
        /* A clicked case */_ => new State(state.A + state.B, state.B), 
        /* B clicked case */_ => new State(state.A, state.A)
    )
);

observable.Subscribe(state => Console.WriteLine($"a = {state.A}, b = {state.B}"));
a.OnNext(Unit.Default);
a.OnNext(Unit.Default);
a.OnNext(Unit.Default);
a.OnNext(Unit.Default);
b.OnNext(Unit.Default);
a.OnNext(Unit.Default);
a.OnNext(Unit.Default);
a.OnNext(Unit.Default);
a.OnNext(Unit.Default);
b.OnNext(Unit.Default);

这是 C# 中依赖的类。其中大部分可以轻松转换为内置 F# 类型。

public class State /*easily replaced with an F# record */
{
    public State(int a, int b)
    {
        A = a;
        B = b;
    }

    public int A { get; }
    public int B { get; }
}

/* easily replaced with built-in discriminated unions and pattern matching */
public static class DiscriminatedUnionExtensions
{
    public static IObservable<DiscriminatedUnionClass<T1, T2>> DiscriminatedUnion<T1, T2>(this IObservable<T1> a, IObservable<T2> b)
    {
        return Observable.Merge(
            a.Select(t1 => DiscriminatedUnionClass<T1, T2>.Create(t1)),
            b.Select(t2 => DiscriminatedUnionClass<T1, T2>.Create(t2))
        );
    }

    public static IObservable<TResult> Unify<T1, T2, TResult>(this IObservable<DiscriminatedUnionClass<T1, T2>> source,
        Func<T1, TResult> f1, Func<T2, TResult> f2)
    {
        return source.Select(union => Unify(union, f1, f2));
    }

    public static TResult Unify<T1, T2, TResult>(this DiscriminatedUnionClass<T1, T2> union, Func<T1, TResult> f1, Func<T2, TResult> f2)
    {
        return union.Item == 1
            ? f1(union.Item1)
            : f2(union.Item2)
        ;
    }
}

public class DiscriminatedUnionClass<T1, T2>
{
    private readonly T1 _t1;
    private readonly T2 _t2;
    private readonly int _item;
    private DiscriminatedUnionClass(T1 t1, T2 t2, int item)
    {
        _t1 = t1;
        _t2 = t2;
        _item = item;
    }

    public int Item
    {
        get { return _item; }
    }

    public T1 Item1
    {
        get { return _t1; }
    }

    public T2 Item2
    {
        get { return _t2; }
    }

    public static DiscriminatedUnionClass<T1, T2> Create(T1 t1)
    {
        return new DiscriminatedUnionClass<T1, T2>(t1, default(T2), 1);
    }

    public static DiscriminatedUnionClass<T1, T2> Create(T2 t2)
    {
        return new DiscriminatedUnionClass<T1, T2>(default(T1), t2, 2);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

反应式编程中流之间的循环依赖关系 的相关文章

随机推荐

  • 使用 Get-Help cmdlet 以相同格式显示基于注释的帮助

    我尝试使用 Get Help cmdlet 以与显示从 XML 文件生成的 cmdlet 帮助主题相同的格式显示基于注释的帮助 执行此操作的能力记录在about Comment based Help http technet microso
  • Tridion GUI Extensions CommandSet 如何映射到 js 方法?

    Tridion GUI 扩展配置如何将名称映射到 JS 文件 例如 我正在使用 Jaime 的你好世界帖子 http jaimesantosalcon blogspot com 2011 02 sdl tridion 2011 ga gui
  • 如何在 C#/Win32 api 中找到 Windows 应用程序执行别名的目标?

    Microsoft Windows 终端 通过 Microsoft Store 安装 创建 0 字节wt exe文件是一个Windows 执行别名 https www tiraniddo dev 2019 09 overview of wi
  • 如何计算(数学)字符串表达式,vb.net

    我不确定我使用的术语是否正确 但我有一个字符串表达式 我希望计算它 这是一个例子 Dim S 4 4 dim result evaluate S some sort of treatment that return 8 我不确定这将如何运作
  • 与 MinGW-64 的静态链接

    我正在尝试使用 64 位 MinGWhttp sourceforge net projects mingw w64 files Toolchains 20targetting 20Win64 Automated 20Builds http
  • java.net.SocketException:连接由对等方重置:服务文件时套接字写入错误

    我正在尝试使用套接字实现 HTTP 服务器 如果客户端 例如浏览器 请求目录 服务器将显示可用文件的列表 当客户端请求文件时就会出现问题 我收到以下错误 java net SocketException Connection reset b
  • 如何在Angular 2中实现AOP

    我是 Angular 2 的新手 但我在 Angular 1 x 方面有很好的经验 我收到错误 找不到模块 aspect js dist lib aspect 下面是我的代码 日志记录 aspect ts import Injectable
  • Android OpenGL图像处理——不使用SurfaceView

    我想使用 OpenGL ES 2 和着色器进行一些背景图像处理 但不绘制到表面 即仅离屏渲染到帧缓冲区 虽然我已经在 iOS 上做到了这一点 但我在 Android 上却遇到了困难 我想我已经接近创建一个工作 OpenGL 上下文 但还没有
  • C++:生成函数调用树

    我想解析项目中当前的 C 文件并列出其中的所有方法 函数 然后生成函数调用和调用者树 F g 您可以参考 doxygen 如何生成调用树 我已经检查了 gccxml 但它没有列出从另一个函数调用的函数 请建议我一些我可以使用的轻量级工具 开
  • 检查数字的递归函数

    编写一个递归函数来检查数字中有多少位可以除以它们后面的数字 例子 84963应该返回 2 因为 8 可以除以 4 6 可以除以 3 我的函数似乎根本没有输出任何内容 include
  • 如何更改表列数据类型[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我有以下专
  • 如何建立/模拟持续的 TCP 连接?

    看起来 WCF TCP 连接不是持久的 第一次 ping 回复需要一段时间 但后续过程花费的时间更少 过了一会儿 又需要很长时间 再次重新连接 服务器 gt 在 net tcp 0 0 0 0 999 上启动 CLIENT gt Conne
  • CSS 只适用于 Safari?

    是否可以添加一个我只想在 Safari 中显示而不是在其他浏览器中显示的 css 块 下面是一个示例 如果您的浏览器是 Safari 或 Chrome 两者共享通用的 Webkit 渲染引擎 则将网站的字体颜色设置为绿色 media scr
  • 自动调整 WebView 大小以适应内容

    我正在开发一个 Xamarin Forms PCL 项目 该项目以 WebView 的形式显示帖子 因此我添加了可点击的内容 例如主题标签 我遇到的问题是 WebView 无法适应其内容的大小 WebView 不加载实际站点我使用以下方法将
  • APC 不记得 CLI 中的缓存

    我正在使用以下命令设置 APC 缓存数组 apc add ips ips 通过命令行 可以通过以下方式检索此缓存数组 apc fetch ips 在同一个脚本中 但是 无法从后续 CLI 脚本或通过 Apache 调用的 PHP 访问它 虽
  • 什么时候 `new Error()` 比 `Error()` 更好?

    ES5 语言规范明确指出 http www ecma international org ecma 262 5 1 sec 15 11 1 that Error foo 做同样的事情new Error foo 但我注意到在野外 时间越长ne
  • 发布一对多关系

    我正在尝试通过 Django REST 框架向我的 Django 模型公开 API 我有一个对象Observation 一个观察可以包含多个已观察到的事物 所以我这样表示 class Observation models Model pho
  • 如何验证 EWS Java API

    我们正在使用 EWS Java API 在 Java 应用程序上使用 Outlook 日历 我在 EWS 上遇到身份验证问题 我在机架空间提供的云 Outlook 帐户上尝试了该应用程序 一切正常 因此我知道凭据是准确的 这是代码 impo
  • 在线性规划中将条件约束转换为线性约束

    我有两个变量 x gt 0 和 y 二进制 0 或 1 并且我有一个常数 z gt 0 如何使用线性约束来描述以下条件 If x z then y 1 else y 0 我试图通过定义另一个二元变量 i 和一个足够大的正常数 U 并添加约束
  • 反应式编程中流之间的循环依赖关系

    在涉足反应式编程时 我经常遇到两个流相互依赖的情况 解决这些案例的惯用方法是什么 一个最小的例子 有按钮 A 和 B 都显示一个值 单击 A 必须将 A 的值增加 B 单击 B 必须将 B 的值设置为 A 我能想到的第一个解决方案 F 中的