如何用F#编写一个通用的递归扩展方法



我很难将一段定义静态、通用扩展、递归扩展方法的C#代码翻译成F#。特别的一段代码是Daniel Smith在Write an Rx"RetryAfter"扩展方法中的Stackoverflow Community Wiki文章。它是这样定义的

public static IObservable<TSource> RetryAfterDelay<TSource, TException>(
    this IObservable<TSource> source,
    TimeSpan retryDelay,
    int retryCount,
    IScheduler scheduler) where TException : Exception
    {
        return source.Catch<TSource, TException>(ex =>
        {
            if (retryCount <= 0)
            {
                return Observable.Throw<TSource>(ex);
            }
            return
                source.DelaySubscription(retryDelay, scheduler)
                .RetryAfterDelay<TSource, TException>(
                  retryDelay, --retryCount, scheduler);
        });
}

我无法想出一种定义函数的方法,这样我就可以在函数内部调用它。我现有的简化版本是这样的,其中编译器告诉The field, constructor or member 'retryAfterDelay' is not defined

open System
open FSharp.Reactive
open System.Reactive
open System.Reactive.Concurrency
open System.Reactive.Linq
open System.Reactive.Threading.Tasks
open System.Runtime.CompilerServices
//Note that to declare .NET compatible extensions methods "correctly" in F#, one
//needs to also add the assembly level extension attribute. There's a good summary
//by Lincoln Atkinson at http://latkin.org/blog/2014/04/30/f-extension-methods-in-roslyn/.
[<assembly:Extension>]
do ()
[<Extension>]
type ObservableExtensions =
    [<Extension>]
    static member inline retryAfterDelay((source: IObservable<_>), (retryDelay: TimeSpan), retryCount, (scheduler: IScheduler)): IObservable<_> =
        source.Catch(fun ex -> source.DelaySubscription(retryDelay, scheduler).retryAfterDelay(retryDelay, retryCount - 1, scheduler))
[<EntryPoint>]
let main argv =
    0

这可能吗?我试着想出一个这个特殊案例的例子,但到目前为止都是徒劳的。

<编辑:现在包含了整个程序。Nugets是Install-Package Rx-MainInstall-Package FSharp.Reactive,使用VS2013编译。NET 4.5.1和FSharp。核心4.3.1.0处于调试模式。

<edit 2:在Record类型的递归成员函数和"rec"关键字处,有一个关于递归成员函数中关键字rec的切线注释。简而言之,得出的结论是递归成员函数中的rec绑定不正确,因此编译器将其标记为错误。

<编辑3:也许实现这一点的一种潜在方法如下。我还没有检查这是否真的有效,这可能需要一些时间,所以我只在这里添加一个中间注释。。。

[<Extension>]
type ObservableExtensions =   
    [<Extension>]
    static member inline retryAfterDelay((source: IObservable<_>), (retryDelay: TimeSpan), retryCount, (scheduler: IScheduler)): IObservable<_> =
        ObservableExtensions.retryAfterDelay(source.DelaySubscription(retryDelay, scheduler), retryDelay, retryCount - 1, scheduler)

<编辑4:根据其他地方的提示和Gustavo的答案,为了尊重带有类型约束的原始代码,我提出了以下

//Note that to declare .NET compatible extensions methods "correctly" in F#, one
//needs to also add the assembly level extension attribute. There's a good summary
//by Lincoln Atkinson at http://latkin.org/blog/2014/04/30/f-extension-methods-in-roslyn/.
[<assembly:Extension>]
do ()
[<Extension>]
type ObservableExtensions =   
    [<Extension>]
    [<CompiledName("PascalCase")>]
    static member inline retryAfterDelay<'TSource, 'TException when 'TException :> System.Exception>(source: IObservable<'TSource>, retryDelay: int -> TimeSpan, maxRetries, scheduler: IScheduler): IObservable<'TSource> =
            let rec go(source: IObservable<'TSource>, retryDelay: int -> TimeSpan, retries, maxRetries, scheduler: IScheduler): IObservable<'TSource> =
                    source.Catch<'TSource, 'TException>(fun ex -> 
                        if maxRetries <= 0 then
                            Observable.Throw<'TSource>(ex)
                        else
                            go(source.DelaySubscription(retryDelay(retries), scheduler), retryDelay, retries + 1, maxRetries - 1, scheduler))
            go(source, retryDelay, 1, maxRetries, scheduler)

的几个注意事项

  1. 我不确定'TSource是否有什么不同,或者以前版本中使用的通配符_是否也一样好。尽管如此,我相信这代表了原始代码
  2. 我修改了接口以包含一个工厂函数来创建延迟。例如,函数可以是let dummyRetryStrategy(retryCount: int) = TimeSpan.FromSeconds(1.0),一个示例用例是let a = Seq.empty<int>.ToObservable().retryAfterDelay(dummyRetryStrategy, 3, Scheduler.Default)
  3. 接口至少可以在调度器方面进行优化,这是代码基本上未经测试的。嗯,也许这应该链接回社区wiki的答案
  4. 这个接口会是其他接口中可用的一个吗。NET语言,如C#和VB.NET。实际上,我在code Review SO上有一篇关于与此非常相关的代码的帖子,所以也许最好在那里处理(我明天会更新它,大约20个小时左右(

一旦完成Type声明,就可以将其用作扩展方法。所以你可以这样写方法:

[<Extension>]
type ObservableExtensions =
    [<Extension>]
    static member retryAfterDelay(source: IObservable<_>, retryDelay: TimeSpan, retryCount, scheduler: IScheduler): IObservable<_> =
        ObservableExtensions.retryAfterDelay(source.Catch(fun ex -> source.DelaySubscription(retryDelay, scheduler)),retryDelay, retryCount - 1, scheduler)

之后你可以立即使用它。如果你需要在同一类的另一个扩展中使用它,你可以通过再次打开类型声明来使用它:

type ObservableExtensions with
    [<Extension>]
    static member anotherExtension (x: IObservable<_>) = x.retryAfterDelay // now you can use it as an extension method

另一种选择是在内部函数中使用letrec

    [<Extension>]
    static member retryAfterDelay(source: IObservable<_>, retryDelay: TimeSpan, retryCount, scheduler: IScheduler): IObservable<_> =
        let rec go (source: IObservable<_>, retryDelay: TimeSpan, retryCount, scheduler: IScheduler): IObservable<_> =
            go (source.Catch(fun ex -> source.DelaySubscription(retryDelay, scheduler)),retryDelay, retryCount - 1, scheduler)
        go (source, retryDelay, retryCount, scheduler)

我更喜欢F#,因为递归在代码中是显式的。

相关内容

  • 没有找到相关文章