سؤال

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 Stackoverflow Community Wiki piece at Write an Rx "RetryAfter" extension method. It's defined like so

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

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.

<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.

<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...

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

//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. 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).
  3. 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.
  4. 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).
هل كانت مفيدة؟

المحلول

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:

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top