如何概括f#选项?

时间:2012-11-13 18:37:15

标签: generics f#

我以'a option when 'a :> IBaseType的形式得到了一些计算结果。有一个类型的树派生自IBaseType,我真的不知道这个特定类型的选项,但重要的是它是特定派生的选项,而不是基本类型。所以我想将它转发到IBaseType option以进一步处理它。由于选项是泛型类型,因此不可能直接进行强制转换(在F#中),而且我必须在Option.map中进行强制转换。没有什么复杂的,类型推断按预期工作...... inference ok 中间铸造选项也按预期解决了...... intermediate inference ok 直到功能完成。此时由于某种原因,类型推断决定原始选项必须已经是IBaseType option类型: inference not ok

中级类型早先已经解决,为什么决定重新分配op的感染类型?当然这会导致运行时异常。看起来像编译器错误,但主要规则是编译器中没有错误。

所以最后这听起来真的很愚蠢:我没有想法如何简单地改造简单的选项。只是为了让图片更清晰:processResultIBaseType option为参数。这是麻烦功能的来源:

(fun (x: obj) -> 
    let op = x :?> _ option
    let upcastOp = op |> Option.map (fun y -> y :> IBaseType)
    upcastOp |> processResult)

有任何想法如何解决这个问题?

3 个答案:

答案 0 :(得分:3)

你是如何制作盒装物品的?最简单的解决方案是打开IBaseType option而不是装箱#IBaseType option来开始。如果由于某种原因这是不可行的,那么你可能需要使用反射。问题是在这段代码中:

let op = x :?> _ option
let upcastOp = op |> Option.map (fun y -> y :> IBaseType)

编译器知道op对于某些'a option when 'a :> IBaseType'a但是没有什么能让编译器弄清'a实际上是什么,因为这种类型不是反映在函数的最终输出中 - 编译器需要提交'a特定类型,并且它可以做出的最佳猜测只是基类型IBaseType。你需要做这样的事情:

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|])    

答案 1 :(得分:1)

我不知道如何推断op的类型。 但我很确定如果你不能按照kvb的建议改变xIBaseType option的类型,你真的必须使用反射。

另一种基于反射的解决方案:

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 :(得分:1)

我支持kvb的解决方案。现在,在我为类似代码做的一些基准测试中,为了绝对性能,我发现避免动态(未知)方法调用是一个优势。以某种方式制作泛型类型的新实例更快。例如:

[<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)

至于问题是什么,以下“直观”推理在.NET中无效:

'T1 :> 'T2
--------------------------
option<'T1> :> option<'T2>

我想说这是典型的类型系统 - 一旦你考虑到它们与整个类型系统的交互,那些看似简单或直观的东西很难或不可能正确实现。