问题
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