问题
I'm getting result of some calculations in form of 'a option when 'a :> IBaseType
. There is tree of types derived from IBaseType
, and I don't really know option of what particular type this one is, but the important thing is that it's option of specific derived, not base type. So I want to upcast it to IBaseType option
to process it further. As option is generic type, it's impossible to do cast directly (in F#), and I have to do cast inside of Option.map. Nothing complicated, type inference works as expected...


IBaseType option
:

Intermediate type was already resolved earlier, why did it decided to reassign infered type of the op
? Sure this leads to runtime exceptions. Looks like compiler error, but the main rule is that there are no errors in compiler.
So in the end it sounds really stupid: I'm out of ideas how to simply upcast simple option. Just to make picture clearer: processResult
takes IBaseType option
as argument. And here is source of troublesome function:
(fun (x: obj) ->
let op = x :?> _ option
let upcastOp = op |> Option.map (fun y -> y :> IBaseType)
upcastOp |> processResult)
Any ideas how to deal with this?
回答1:
I don't know how the type of op
is inferred here.
But I'm pretty sure that if you can't change the type of x
to IBaseType option
as suggested by kvb, you really have to use reflection.
A alternative reflection-based solution:
let f (x:obj) =
match x with
| null -> None // x is None
| _ -> match x.GetType().GetProperty("Value") with
| null -> None // x is not an option
| prop ->
let v = prop.GetValue( x, null )
Some (v :?> IBaseType)
回答2:
How are you producing the boxed objects in the first place? The easiest solution would be to box an IBaseType option
rather than boxing #IBaseType option
s to begin with. If that's not feasible for some reason, then you'll probably need to use reflection. The issue is that in this block of code:
let op = x :?> _ option
let upcastOp = op |> Option.map (fun y -> y :> IBaseType)
the compiler knows that op
is a 'a option when 'a :> IBaseType
for some 'a
but there's nothing that allows the compiler to figure out what 'a
actually is because this type isn't reflected in the final output of your function - the compiler needs to commit to a specific type for 'a
, and the best guess it can make is just the base type IBaseType
. You would need to do something like this instead:
type ResultProcessor =
static member ProcessResult<'a when 'a :> IBaseType> (op:'a option) =
let upcastOp = op |> Option.map (fun y -> y :> IBaseType)
upcastOp |> processResult
fun (x:obj) ->
let ty = x.GetType() // must be option<something>
let [| tyArg |] = ty.GetGenericArguments()
typeof<ResultProcessor>.GetMethod("ProcessResult").MakeGenericMethod(tyArg).Invoke(null, [|x|])
回答3:
I support kvb's solution. Now, in some benchmarks I did for similar code, for absolute performance I found it an advantage to avoid dynamic (unknown) method invocations. Somehow making new instantiations of generic types is faster. For example:
[<AbstractClass>]
type BaseResultProcessor() =
abstract member ProcessResult : obj -> option<IBaseType>
[<Sealed>]
type ResultProcessor<'T when 'T :> IBaseType>() =
inherit BaseResultProcessor()
override this.ProcessResult(x: obj) =
match x :?> option<'T> with
| Some x -> Some (x :> IBaseType)
| None -> None
module Example =
let run (x: obj) =
let ty = x.GetType()
let tyArg = ty.GetGenericArguments().[0]
let p =
typedefof<ResultProcessor<_>>.MakeGenericType(tyArg)
|> Activator.CreateInstance :?> BaseResultProcessor
p.ProcessResult(x)
As to what is the problem, the following "intuitive" reasoning is invalid in .NET:
'T1 :> 'T2
--------------------------
option<'T1> :> option<'T2>
I would say this is typical of type systems - things that appear easy or intuitive are difficult or impossible to implement correctly once you take into account their interactions with the type system as a whole.
来源:https://stackoverflow.com/questions/13366647/how-to-generalize-f-option