Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
F# 如何更改Rx Builder实现以修复堆栈溢出异常?_F#_System.reactive_Computation Expression - Fatal编程技术网

F# 如何更改Rx Builder实现以修复堆栈溢出异常?

F# 如何更改Rx Builder实现以修复堆栈溢出异常?,f#,system.reactive,computation-expression,F#,System.reactive,Computation Expression,我正试图设计一个Rx生成器,在F#计算表达式语法中使用反应式扩展。我如何修复它,使它不会爆炸?如下面的Seq示例。 是否有计划将RxBuilder的实现作为反应式扩展的一部分或作为.NET Framework未来版本的一部分提供 open System open System.Linq open System.Reactive.Linq type rxBuilder() = member this.Delay f = Observable.Defer f member

我正试图设计一个Rx生成器,在F#计算表达式语法中使用反应式扩展。我如何修复它,使它不会爆炸?如下面的Seq示例。 是否有计划将RxBuilder的实现作为反应式扩展的一部分或作为.NET Framework未来版本的一部分提供

open System
open System.Linq
open System.Reactive.Linq

type rxBuilder() =    
    member this.Delay f = Observable.Defer f
    member this.Combine (xs: IObservable<_>, ys : IObservable<_>) = 
        Observable.merge xs ys      
    member this.Yield x = Observable.Return x
    member this.YieldFrom (xs:IObservable<_>) = xs

let rx = rxBuilder()

let rec f x = seq { yield x 
                    yield! f (x + 1) }

let rec g x = rx { yield x 
                    yield! g (x + 1) }


//do f 5 |> Seq.iter (printfn "%A")

do g 5 |> Observable.subscribe (printfn "%A") |> ignore

do System.Console.ReadLine() |> ignore
开放系统
开放系统
开放系统.Reactive.Linq
键入rxBuilder()=
此成员。延迟f=可观察。延迟f
成员this.Combine(xs:IObservable,ys:IObservable)=
可观察的。合并xs和ys
此成员。收益率x=可观察。回报率x
成员this.YieldFrom(xs:IObservable)=xs
设rx=rxBuilder()
设rec f x=seq{yield x
屈服!f(x+1)}
设rec g x=rx{x
收益率!g(x+1)}
//do f 5 |>序号(打印“%A”)
Dog5 |>Observable.subscribe(printfn“%A”)|>ignore
执行System.Console.ReadLine()|>忽略

一个简短的回答是,Rx框架不支持使用这样的递归模式生成可观察对象,因此这不容易做到。用于F#序列的
合并
操作需要一些可观测数据无法提供的特殊处理。Rx框架可能希望您使用
Observable.generate
生成Observable,然后使用LINQ查询/F#computement builder来处理它们

无论如何,这里有一些想法-

首先,您需要将
Observable.merge
替换为
Observable.Concat
。第一种方法并行运行两个观测值,而第二种方法首先从第一个观测值中生成所有值,然后从第二个观测值中生成值。此更改后,代码段将在堆栈溢出之前至少打印约800个数字

堆栈溢出的原因是
Concat
创建一个调用
Concat
的可观察对象,以创建另一个调用
Concat
等的可观察对象。解决此问题的一种方法是添加一些同步。如果您使用的是Windows窗体,那么您可以修改
Delay
,以便它在GUI线程上调度可观察对象(这将丢弃当前堆栈)。这是一张草图:

type RxBuilder() =   
  member this.Delay f = 
      let sync = System.Threading.SynchronizationContext.Current 
      let res = Observable.Defer f
      { new IObservable<_> with
          member x.Subscribe(a) = 
            sync.Post( (fun _ -> res.Subscribe(a) |> ignore), null)
            // Note: This is wrong, but we cannot easily get the IDisposable here
            null }
  member this.Combine (xs, ys) = Observable.Concat(xs, ys)
  member this.Yield x = Observable.Return x
  member this.YieldFrom (xs:IObservable<_>) = xs
type RxBuilder()=
此成员。延迟f=
让sync=System.Threading.SynchronizationContext.Current
设res=可观测的
{新IObservable with
成员x.认购(a)=
sync.Post((乐趣->res.Subscribe(a)|>忽略),null)
//注意:这是错误的,但我们无法在此处轻松获得IDisposable
空}
成员this.Combine(xs,ys)=Observable.Concat(xs,ys)
此成员。收益率x=可观察。回报率x
成员this.YieldFrom(xs:IObservable)=xs
要正确实现这一点,您必须编写自己的
Concat
方法,这相当复杂。其想法是:

  • Concat返回一些特殊类型,例如
    IConcatenatedObservable
  • 当递归调用该方法时,您将创建一个相互引用的
    IConcatenatedObservable
  • Concat
    方法将查找此链,当有三个对象时,它将删除中间的一个对象(始终保持链的长度不超过2)

这对于StackOverflow的答案来说有点太复杂了,但对于Rx团队来说,这可能是一个有用的反馈。

如果我们从这个计算表达式(又名Monad)中删除语法糖,我们将得到:

let rec g x = Observable.Defer (fun () -> Observable.merge(Observable.Return x, g (x + 1) )
或者在C#中:

publicstaticiobservable g(intx)
{
返回可观察的延迟(()=>
{
返回可观察合并(可观察返回(x),g(x+1));
});
}

这绝对不是尾部递归。我想如果你能使它成为尾部递归的,那么它可能会解决你的问题

像这样的东西怎么样

type rxBuilder() =    
   member this.Delay (f : unit -> 'a IObservable) = 
               { new IObservable<_> with
                    member this.Subscribe obv = (f()).Subscribe obv }
   member this.Combine (xs:'a IObservable, ys: 'a IObservable) =
               { new IObservable<_> with
                    member this.Subscribe obv = xs.Subscribe obv ; 
                                                ys.Subscribe obv }
   member this.Yield x = Observable.Return x
   member this.YieldFrom xs = xs

let rx = rxBuilder()

let rec f x = rx { yield x 
                   yield! f (x + 1) }

do f 5 |> Observable.subscribe (fun x -> Console.WriteLine x) |> ignore

do System.Console.ReadLine() |> ignore
type rxBuilder()=
成员此。延迟(f:unit->'a IObservable)=
{新IObservable with
成员this.Subscribe obv=(f()).Subscribe obv}
成员this.Combine(xs:'a IObservable,ys:'a IObservable)=
{新IObservable with
成员this.Subscribe obv=xs.Subscribe obv;
ys.订阅obv}
此成员。收益率x=可观察。回报率x
成员this.YieldFrom xs=xs
设rx=rxBuilder()
设rec f x=rx{x
屈服!f(x+1)}
Dof5 |>Observable.subscribe(funx->Console.WriteLine x)|>忽略
执行System.Console.ReadLine()|>忽略
(为试验RxBuilder而创建)


xs一次性电源没有接线。一旦我试着把一次性塑料线连接起来,它就会回到炸掉塑料堆的状态。

这一问题已经解决了。这里有一个。

请注意,Rx v2.0(如前所述)中已对这一点进行了修正,更一般地说,所有排序运算符(Concat、Catch、OnErrorResumeNext)以及命令式运算符(If、While等)

基本上,您可以将这类运算符视为在终端观察者消息中订阅另一个序列(例如,Concat在接收到当前未完成消息时订阅下一个序列),这就是尾部递归类比的由来


在RXV2.0中,所有尾部递归订阅都被扁平化为一个队列状的数据结构,以便一次处理一个,与下游观察者对话。这避免了观察者为了连续的序列订阅而互相交谈的无限增长。

这可能会引起您的兴趣:您是否查看了最新Rx版本中的
Expand
操作符?它处理某些递归场景。您将如何应用Observable。扩展到这个问题?你有什么链接可以解释Expand的语义吗?@Ramon:那是Holo
type rxBuilder() =    
   member this.Delay (f : unit -> 'a IObservable) = 
               { new IObservable<_> with
                    member this.Subscribe obv = (f()).Subscribe obv }
   member this.Combine (xs:'a IObservable, ys: 'a IObservable) =
               { new IObservable<_> with
                    member this.Subscribe obv = xs.Subscribe obv ; 
                                                ys.Subscribe obv }
   member this.Yield x = Observable.Return x
   member this.YieldFrom xs = xs

let rx = rxBuilder()

let rec f x = rx { yield x 
                   yield! f (x + 1) }

do f 5 |> Observable.subscribe (fun x -> Console.WriteLine x) |> ignore

do System.Console.ReadLine() |> ignore