如何累积可观察物

时间:2018-12-21 17:09:51

标签: functional-programming f#

我应该定义一个函数,该函数将返回IObservable <'u>

accumulate : ('t -> 'a -> 't * 'u option) -> 't -> IObservable<'a> -> IObservable<'u>

因此,我的函数ft obs'将obs的可观察事件累积到类型为't的累加器中,并且当'snd(f acc a)'对观察到的事件'a'求值为'Some u'时,发出可观察事件u

到目前为止,我已经实现了以下功能:

let accumulate (f:'t -> 'a -> 't * 'u option) t obs = 
 Observable.scan (fun _ x -> snd (f t x)) None obs

我真的不明白这种可观察的扫描方式是如何工作的,在这种情况下,我的函数返回IObservable <'u选项>。我该如何解决?我在正确的轨道上吗?

2 个答案:

答案 0 :(得分:2)

函数fun _ x -> snd (f t x)不完整。提示是,第一个参数_被忽略,结果元组的第一部分被调用snd丢弃。

没有累积,因为f t x始终使用原始传递给t的相同值accumulate进行调用。原来的t应该是初始值,应该作为第二个参数的一部分传递给scan

f:'t -> 'a -> 't * 'u option生成的元组的第一部分是累加值。因此,该部分需要返回到scan,以便再次传递到f并不断积累。

在您的问题中,要求是积累并在元组的第二部分为Some 'u时传递事件。因此,问题是如何做到这两者:累加't和过滤'u

答案是通过将累积值与Some 'u所做的f相结合。因此,您需要将元组保持为scan状态,然后再使用choosesnd仅保留第二部分。

这是您要寻找的:

let accumulate (f:'t -> 'a -> 't * 'u option) t obs =
    obs
    |> Observable.scan (fun (acc, _) x -> f acc x) (t, None)
    |> Observable.choose snd

了解scan

scan是通过将变化的状态与一系列值一起传递给函数来承载变化中状态的函数。特别是,它可以用于累加值,例如int运行总计:

let keepTotal obs =
    obs
    |> Observable.scan (fun total v -> total + v) 0

这等效于在具有可变total的命令式代码中执行此操作:

let mutable total = 0

let keepTotal2 obs =
    obs
    |> Observable.map (fun v -> 
        total <- total + v
        total
    )

注意两个版本如何具有相同的元素:

  • 初始值:0
  • 累加器功能:total + v

当然,即使第二个版本使用map,它也是不好的功能代码,因为它使用了外部可变变量,这是一个很大的NO NO。

您原来的问题可以用相同的方法解决:

let accumulate2 (f:'t -> 'a -> 't * 'u option) t obs =
    let mutable acc = t
    obs
    |> Observable.choose (fun x ->
        let acc2, uOp = f acc x
        acc <- acc2
        uOp
    )

即使此变量使用的可变变量在函数式编程中也是很丑陋的(并且是不必要的),但在功能上还可以,因为变量acc是内部变量,accumulate2之外的任何代码都看不到它。仍然很丑。

答案 1 :(得分:1)

您可以在Observable.choose之后链接Observable.scan,以获得正确的类型签名

let accumulate (f:'t -> 'a -> 't * 'u option) t obs =
    obs
    |> Observable.scan (fun _ x -> snd (f t x)) None
    |> Observable.choose id