我很难将一段定义静态、通用扩展、递归扩展方法的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-Main
和Install-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)
的几个注意事项
- 我不确定
'TSource
是否有什么不同,或者以前版本中使用的通配符_
是否也一样好。尽管如此,我相信这代表了原始代码 - 我修改了接口以包含一个工厂函数来创建延迟。例如,函数可以是
let dummyRetryStrategy(retryCount: int) = TimeSpan.FromSeconds(1.0)
,一个示例用例是let a = Seq.empty<int>.ToObservable().retryAfterDelay(dummyRetryStrategy, 3, Scheduler.Default)
- 接口至少可以在调度器方面进行优化,这是代码基本上未经测试的。嗯,也许这应该链接回社区wiki的答案
- 这个接口会是其他接口中可用的一个吗。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
另一种选择是在内部函数中使用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)
我更喜欢F#,因为递归在代码中是显式的。