将一段异步C#代码转换为F#(带有Reactive Extensions和FSharpx)

时间:2014-08-09 20:57:05

标签: f# system.reactive c#-to-f#

过了一会儿,我在SO中翻阅了一些Rx代码并遇到了问题How to implement polling using Observables?

从F#角度来看 是在F#parlor中使用(自定义)Either类型或Choice。唉,我从很多层面开始都是错误的翻译,从类型开始。

但是,这可能会被用作更广泛的教育工具,而不仅仅是放弃尝试翻译。 有人可以借出一个功能强大的F#大脑并帮助翻译以下C#代码吗?

我使用了F#TaskBuilder,但最近才发现它没有实现TryWith part of a computation builder中的TaskBuilder。因此,翻译第一段C#代码(或者可能应该转到async路径)可能变得很困难,在源SO链接中提供的另一个版本没有使用异步结构。关于这个问题或运气不好,我也无法翻译。

我得到的错误是那种:

Type mismatch. Expecting a IScheduler -> CancellationToken -> Task but given a 'a * CancellationToken -> 'b The type 'IScheduler' does not match the type ''a * CancellationToken'

Type mismatch. Expecting a IObservable<Choice<'TResult,exn>> but given a IObservable<'TResult> The resulting type would be infinite when unifying ''TResult' and 'Choice<'TResult,exn>'

等等。

  • 安装包FSharpx
  • 安装包Rx-Main

-

    [<Extension>]
    type ObservableExtensions() = 
        static member inline poll<'TResult, 'TArg>(asyncFunction: 'TArg -> IObservable<'TResult>, parameterFactory: 'TArg, interval:TimeSpan, scheduler: IScheduler): IObservable<Choice<'TResult, exn>> =

            Observable.Create<'TResult>(fun(observer:IObserver<'TResult>) ->
                let task = new TaskBuilder()                                     
                let t(ctrl:IScheduler, ct:CancellationToken) = task {
                    while not <| ct.IsCancellationRequested do
                        try
                            let! result = asyncFunction(parameterFactory)
                            observer.OnNext(Choice1Of2(result))
                        with ex ->
                            observer.OnNext(Choice2Of2(ex))
                        do! ctrl.Sleep(interval, ct)
                }

                scheduler.ScheduleAsync(Func<IScheduler, CancellationToken, Task>(t)))

和相应的C#代码(1):

public IObservable<Either<Exception, TResult>> Poll<TResult, TArg>(
Func<TArg, IObservable<TResult>> asyncFunction,
Func<TArg> parameterFactory,
TimeSpan interval,
IScheduler scheduler)
{
return Observable.Create<Either<Exception, TResult>>(observer =>
{
    return scheduler.ScheduleAsync(async (ctrl, ct) => {
        while(!ct.IsCancellationRequested)
        {
            try
            {
                var result = await asyncFunction(parameterFactory());
                observer.OnNext(Either.Right<Exception,TResult>(result));
            }
            catch(Exception ex)
            {
                observer.OnNext(Either.Left<Exception, TResult>(ex));
            }
            await ctrl.Sleep(interval, ct);
        }
    });        
});    
}

替代C#代码

public IObservable<Either<Exception, TResult>> Poll2<TResult, TArg>(
    Func<TArg, IObservable<TResult>> asyncFunction,
    Func<TArg> parameterFactory,
    TimeSpan interval,
    IScheduler scheduler)
{
    return Observable.Create<Either<Exception, TResult>>(
        observer =>
            Observable.Defer(() => asyncFunction(parameterFactory()))
                      .Select(Either.Right<Exception, TResult>)
                      .Catch<Either<Exception, TResult>, Exception>(
                        ex => Observable.Return(Either.Left<Exception, TResult>(ex)))
                      .Concat(Observable.Defer(
                        () => Observable.Empty<Either<Exception, TResult>>()
                                        .Delay(interval, scheduler)))
                      .Repeat().Subscribe(observer));
}

请注意,这与How to write a generic, recursive extension method in F#?有一些相似之处,因此与Write an Rx “RetryAfter” extension method extension method

相似

&lt; edit:为了补充MisterMetaphor的优秀答案,我也会在这里添加一个没有间隔的版本。

type Observable with
    static member Poll2(f: unit -> IObservable<_>, interval: TimeSpan, sched: IScheduler) : IObservable<_> =
        Observable.Create<_>(fun observer ->
            Observable.Defer(f)
                .Select(Choice1Of2)
                .Catch(Choice2Of2 >> Observable.Return)
                .Concat(Observable.Defer(fun _ -> Observable.Empty().Delay(interval, sched)))
                .Repeat()
                .Subscribe(observer))

    static member Poll2(f: 'a -> IObservable<_>, argFactory: unit -> 'a, interval: TimeSpan, sched: IScheduler) =
        Observable.Poll2(argFactory >> f, interval, sched)

我不确定是否需要这样做,但是对于在here中发现的F#中的.Subscribe(至少是Visual F#3.1.1.0)中的一个微妙错误的适当注释,更多here)。

1 个答案:

答案 0 :(得分:2)

如果您不必使用Observable.Create,则可以使用Observable.Interval获得类似的结果:

type Observable with
    static member Poll(f : unit -> IObservable<_>, interval : TimeSpan, sched : IScheduler) : IObservable<_> =
        Observable.Interval(interval, sched)
            .SelectMany(fun _ ->
                Observable.Defer(f)
                    .Select(Choice1Of2)
                    .Catch(Choice2Of2 >> Observable.Return))

    // An overload that matches your original function
    static member Poll(f : 'a -> IObservable<_>, argFactory : unit -> 'a, interval : TimeSpan, sched : IScheduler) =
        Observable.Poll(argFactory >> f, interval, sched)

我对此实现的喜欢是,您不必直接使用调度程序和Observable.Create。我认为你应该总是使用现有的组合器/操作器,除非你绝对不得不这样做。

此外,Observable.Interval使用SchedulePeriodic(明显here),这可能比基于Task的实施更有效和正确。