如何在F#中编写通用的递归扩展方法?

时间:2014-05-01 08:08:26

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

我正在努力翻译一段C#代码,它定义了一个静态的,通用的扩展,递归扩展方法到F#。特定的代码是Daniel Smith在Write an Rx "RetryAfter" extension method的Stackoverflow社区维基文章。它的定义如此

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

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

&lt;编辑:现在包含了整个程序。 Nugets为Install-Package Rx-MainInstall-Package FSharp.Reactive,在调试模式下使用VS 2013,.NET 4.5.1和FSharp.Core 4.3.1.0进行编译。

&lt;编辑2:在Record-type recursive member functions and the “rec” keyword的递归成员函数中有关于关键字rec的切线注释。简而言之,它总结了递归成员函数中的rec绑定是不正确的,因此编译器将其标记为错误。

&lt;编辑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)

&lt;编辑4: 从其他地方获取线索并 Gustavo 回答并使用type constraints兑现原始代码段,我想出了以下内容

//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. 至少关于调度程序可以抛光接口,这是代码基本上未经测试。嗯,也许这应该与社区维基回答相关联。
  4. 接口是否可以使用其他.NET语言(如C#和VB.NET)。我在Code Review SO中实际上a post pending on a code与此非常相关,所以也许它最好在那里处理(明天我会在大约二十个小时左右更新它)。

1 个答案:

答案 0 :(得分:3)

完成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声明来使用它:

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#,因为递归在代码中是显式的。