如何使用FSharp.Control.Reactive观察计算实现Observable.take?

时间:2017-04-28 15:04:52

标签: f# system.reactive

我试图理解如何从FSharp.Control.Reactive观察计算工作,所以我重新实现了Observable.take组合

这是我最初的尝试:

let myTake (n : int) (source : IObservable<'a>) : IObservable<'a> =
    let rec go counter : IObservable<'a> =
        observe {
            let! v = source
            if counter < n
            then yield v
                 yield! go (counter + 1)
        }
    go 0

但是,当我运行以下测试时:

use subcription =
    subj
    |> myTake 2
    |> Observable.subscribe (printfn "next:%d")

subj.OnNext 1
subj.OnNext 2
subj.OnNext 3

waitForKey "my take"

我得到了输出:

next:1 
next:2 
next:2 
next:3 
next:3 
next:3 

我该如何解决这个问题?

我还尝试在使用observable.ofSeq创建的observable上运行myTake,它甚至更糟糕,即它只是重复生成输入序列几次。我假设它与ofSeq返回冷可观察但不完全理解行为的事实有关。

我怎样才能使它与冷可观察量一起使用?

谢谢!

2 个答案:

答案 0 :(得分:3)

我对model(params) { return this.store.queryRecord('name', params); } 计算构建器不太熟悉,但快速查看源代码显示observable操作(位于Bind后面)是{{ 3}}。 let!操作将启动每次事件发生的其余工作流程,因此您看到的行为是预期的行为。

来自implemented using the Rx SelectMany operation的图片:

this article illustrates the behaviour well

我不确定使用SelectMany计算构建器实现Observable.take的好方法 - 坦率地说,我一直认为observable不是特别适合F#计算表达式,因为计算表达式带来的通常直觉根本不适用于基于推送的可观察量。

我认为如果你只是编写内置操作就可以解决observable的问题,但是当你需要实现自己的自定义原语时它们并不是特别好 - 而且大多数时候,我只是使用F#代理实现逻辑并将其包装并可观察。

答案 1 :(得分:2)

我同意托马斯的观点,即建造者的本地操作似乎不支持你想要的东西。但是,我的总体结论与他不同 - 我不明白为什么可观察量对于计算表达式而言比计算表达式更有问题。

首先,考虑如何在序列表达式中编写take。在那里,let!不受支持;你需要使用for循环。但是你的代码类似于:

let rec go counter = seq {
    for v in source do
        if counter < n then
            yield v
            yield! go (counter + 1)
}

从一个序列中取出就像从一个可观察者那里获取一样:计数器有效地告诉你循环通过笛卡尔积的次数,这不是你想要的。一种解决方案是使计数器变为可变并将其移动到表达式中(然后不再需要递归):

seq {
    let mutable counter = 0
    for v in source do
        if counter < n then
            yield v
            counter <- counter + 1
}

这适用于序列,但是对于可观察对象呢? observe也支持for循环,但仅用于循环序列而不是可观察的循环。但是,我们可以通过重载构建器的For方法来删除此限制:

type FSharp.Control.Reactive.Builders.ObservableBuilder with
    member __.For(o, f) =
        FSharp.Control.Reactive.Observable.bind f o

现在我们可以像对序列一样轻松地编写take

let myTake (n : int) (source : IObservable<'a>) : IObservable<'a> =
    observe {
        let mutable counter = 0
        for v in source do
            if counter < n then
                yield v
                counter <- counter + 1
    }

您可以验证它是否有效。