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

时间:2021-02-13 20:57:37

I'm struggling somewhat to translate a piece of C# code that defines a static, generic extension, recursive extension method to F#. The particular piece of code is Daniel Smith's * Community Wiki piece at Write an Rx "RetryAfter" extension method. It's defined like so

我正在努力翻译一段c#代码,该代码定义了一个静态的、通用的扩展、对f#的递归扩展方法。这段代码是Daniel Smith的*社区维基文章,写了一个Rx“RetryAfter”扩展方法。它的定义

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);
        });
}

I'm unable to come up with a way to define the function so that I could call it inside the function. A current, simplified version I have is like so, wherein the compiler tells The field, constructor or member 'retryAfterDelay' is not defined

我无法给出定义函数的方法,这样我就可以在函数中调用它了。我有一个当前的简化版本,其中编译器告诉字段,构造函数或成员的“retryAfterDelay”没有定义。

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

Should this be possible? I've tried to come up with an example of this particular case, but thus far in vain.

这可能吗?我试着举出一个例子来说明这个例子,但到目前为止都是徒劳的。

<edit: Now a whole program included. Nugets are Install-Package Rx-Main and Install-Package FSharp.Reactive, compiled with VS 2013, .NET 4.5.1 and FSharp.Core 4.3.1.0 in Debug mode.

<编辑:现在包括了一个完整的程序。nuget是安装包rx-main和install-package fsharp。使用vs 2013,。net 4.5.1和fsharp编译。在调试模式下的核心4.3.1.0。< p>

<edit 2: There's a tangential note regarding the keyword rec in recursive member functions at Record-type recursive member functions and the “rec” keyword. In short, it's concluded the rec binding in recursive member functions is incorrect and thus compiler flags it as an error.

<编辑2:在记录类型递归成员函数和“rec”关键字的递归成员函数中,有一个关于关键字rec的切向注释。简而言之,它的结论是递归成员函数中的rec绑定是不正确的,因此编译器将其标记为错误。< p>

<edit 3: Maybe a potential way to achieve this is as follows. I haven't yet checked if this actually works, it may take some time so I'll just add this here an intermediary note...

<编辑3:可能实现这一目标的方法如下。我还没有检查这个是否有效,可能需要一些时间所以我在这里加上一个中间的注释…< p>

[<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)

<edit 4: Taking cues from elsewhere and Gustavo's answer and to honor the original piece of code with the type constraints, I came up with the following

<编辑4:从其他地方获取线索,gustavo的答案,并尊重原始代码的类型限制,我提出了如下。< p>

//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)

A few notes

一些笔记

  1. I'm uncertain if 'TSource makes any difference or would the wildcard _ as used in previous versions be just as good. Nevertheless, I believe this represents the original code.
  2. 我不确定“TSource是否会产生任何影响”,或者在以前的版本中使用的通配符是否也一样好。不过,我认为这代表了原始代码。
  3. I modified the interface to include a factory function to create a delay. The function could be, for instance, be let dummyRetryStrategy(retryCount: int) = TimeSpan.FromSeconds(1.0) and an example use case would be let a = Seq.empty<int>.ToObservable().retryAfterDelay(dummyRetryStrategy, 3, Scheduler.Default).
  4. 我修改了接口,以包含工厂函数来创建延迟。例如,该函数可以是let dummyRetryStrategy(retryCount: int) = TimeSpan.FromSeconds(1.0)和一个示例用例将让a = Seq.empty . toobservable()。retryAfterDelay(dummyRetryStrategy 3 Scheduler.Default)。
  5. The interface could be polished at least regarding the scheduler, and this is code is essentially untested. Hmm, maybe this should be linked back to the community wiki answer.
  6. 至少对于调度器来说,接口是可以擦亮的,而这是代码基本上没有经过测试。嗯,也许这应该和社区维基的答案联系起来。
  7. Would the interface be a usable one from other .NET languages such as C# and VB.NET. I have actually a post pending on a code very related to this at Code Review SO, so maybe it's best handled there (I'll update it tomorrow, in about twenty hours or so).
  8. 接口是否可以从其他。net语言(如c#和VB.NET)中使用。实际上,我在代码审查中有一个与此相关的代码,所以可能最好在那里处理(我将在明天更新它,大约20个小时左右)。

1 个解决方案

#1


3  

You can use it as an extension method once you finish the Type declaration. So you can write the method like this:

在完成类型声明之后,可以将其用作扩展方法。你可以这样写:

[<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)

After that you can immediately use it. If you need it in another extension from the same class you can use it by re-opening again the Type declaration:

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

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

The alternative is using let and rec inside an internal function:

另一种方法是在内部函数中使用let和rec:

    [<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)

Which I prefer for F# since the recursion is explicit in the code.

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

#1


3  

You can use it as an extension method once you finish the Type declaration. So you can write the method like this:

在完成类型声明之后,可以将其用作扩展方法。你可以这样写:

[<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)

After that you can immediately use it. If you need it in another extension from the same class you can use it by re-opening again the Type declaration:

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

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

The alternative is using let and rec inside an internal function:

另一种方法是在内部函数中使用let和rec:

    [<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)

Which I prefer for F# since the recursion is explicit in the code.

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