Calling generic function with 'params' from F# (Observable.StartWith)

馋奶兔 提交于 2019-12-11 03:21:52

问题


Edit: Note that, as Daniel and latkin noted in an answer and a comment below, this question involved a bug in F# that seems to have been fixed in early 2014.

I'm trying to write a curried wrapper for Observable.StartWith. I'm using the prerelease Reactive Extensions 2.0, and the VS11 beta. My desired result would be startWith : 'a -> IObservable<'a> -> IObservable<'a>. The obvious implementation would be something like:

let startWith 
        (value : 'a) 
        (observable : IObservable<'a>) 
        : IObservable<'a> =
    Observable.StartWith(observable, [| value |])

The intended overload of Observable.StartWith is StartWith<'TSource>(source : IObservable<'TSource>, params values: 'TSource[]) : IObservable<'TSource>.

The compiler throws a confusing error: This method expects a CLI 'params' parameter in this position. 'params' is a way of passing a variable number of arguments to a method in languages such as C#. Consider passing an array for this argument.

I am passing an array. I also tried not passing an array, by omitting the [| |], which leads to a unique-overload-resolution failure. (Presumably due to the possibility that 'a could be System.Reactive.Concurrency.IScheduler, matching the other overload.) I also tried using F# 2.0/VS2010, which gives the same result. I couldn't locate any online discussion of this sort of situation or of the compiler error message.

I can't think of any other way to implement this. Note that in cases where the type parameter can be determined, it's not a problem. For instance, let prependZero : int -> IObservable<int> -> IObservable<int> = fun n o -> o.StartWith(n) works fine. But a generic version would be nice.


回答1:


It looks like a problem with type inference surrounding generic param arrays. Even a simple case, not involving overload resolution, has problems:

type A() = 
  static member M<'T>([<ParamArray>] args: 'T[]) = args

//None of these work
let m1 arg = A.M([|arg|])
let m2 args = A.M(args)
let m3<'T> (args:'T[]) = A.M<'T>(args)

Non-generic versions work:

type B() = 
  static member M([<ParamArray>] args: obj[]) = args

//Both of these are okay
let m1 arg = B.M([|arg|])
let m2 args = B.M(args)

EDIT

I emailed fsbugs and they responded that this is a bug. Here are some workarounds they suggested.

let m1 arg = A.M<obj>([|arg|])
let m2 args = A.M<obj>(args)
let m3 (args:obj[]) = A.M<obj>(args)
let m4 (arg:obj) = A.M<obj>(arg)
let m5 arg1 arg2 = A.M<obj>(arg1,arg2)
let m6 (arg1:'T) = A.M<'T>(arg1)
let m7 (arg1:'T) (arg2:'T) = A.M<'T>(arg1,arg2)
let m8 (arg1:'T) (arg2:'T) = A.M(arg1,arg2)
let m9 (arg1:'T) = A.M(arg1)
let m10<'T> arg1 arg2 = A.M<'T>(arg1,arg2)
let m11<'T> (arg1:'T) (arg2:'T) = A.M<'T>(arg1,arg2)



回答2:


You do not need to wrap your single value into single element array in order for it to match the last ParamArray argument of Observable.StartWith, just scalar value is OK (these samples may help to understand why).

But then generic type of value creates an ambiguity between two available overloads for Observable.StartWith. Disambiguation can be achieved through forcing of three-agrument overload by explicitly placing the implicit type of IScheduler from the two-argument overload to the argument list, prepending the value, as below:

let startWith (value: 'a) observable =
    Observable.StartWith(observable, Scheduler.CurrentThread, value) 

Now your code should compile and work. A quick check confirms this:

Observable.Range(1,2)
|> startWith 10
|> fun x -> x.Subscribe(printf "%d ")

outputs as expected 10 1 2.

Update

For Rx 2.0 beta the Scheduler reference would be slightly different, the rest of the answer stays unchanged:

let startWith (value: 'a) (observable: IObservable<'a>) = 
    Observable.StartWith(observable, Concurrency.Scheduler.CurrentThread, value)


来源:https://stackoverflow.com/questions/11068527/calling-generic-function-with-params-from-f-observable-startwith

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!