How to generalize f# option?

我们两清 提交于 2019-12-10 17:09:43

问题


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

intermediate casted option is also resolved as expected...

until function is complete. At this point for some reason type inference decided that original option must be already of type 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 options 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

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