与泛型类型和装箱/拆箱交互

时间:2014-04-25 13:59:02

标签: generics f# covariance

我有兴趣了解更多有关以下内容的可能性。给定一个约束为obj类型的集合和以下函数:

let myList = new ResizeArray<obj>()

let addToMyListTuple<'a> (item : string * 'a) =
    let boxed = box item
    let unboxed = unbox<string * 'a> boxed
    let item1 = match unboxed with first, _ -> first
    Console.WriteLine(sprintf "%A" item1)
    myList.Add(boxed)

与这些2的交互给出了预期的结果,并且无论第二部分中的关联类型如何,都可以使用元组的字符串部分。

addToMyListTuple("integer", 3)
addToMyListTuple("float", 3.0)
addToMyListTuple("string", "string")
addToMyListTuple("tuple", (3, "integer"))

但是,我希望可能的是,我可以在稍后的时间与列表中的项目进行交互,并以这样的方式取消对obj的打开,即访问元组的字符串部分是可能的。

myList
|> Seq.iter(fun x ->
    let unboxed = unbox<string * 'a> x
    let item1 = match unboxed with first, _ -> first
    Console.WriteLine(sprintf "%A" item1)
)

运行它会给我编译时警告

This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type 'obj'.

和运行时异常

System.InvalidCastException: Unable to cast object of type 'System.Tuple`2[System.String,System.Int32]' to type 'System.Tuple`2[System.String,System.Object]'.

还有其他方法可以完成此行为吗?

2 个答案:

答案 0 :(得分:2)

如果你展示了更多你正在尝试做的事情,可能有更好的方法使它变成多态(例如,工会),但你可以用类型测试做到这一点:

let tuples = ResizeArray()
let addToTuples (k, v) = tuples.Add(k, box v)
addToTuples ("int", 3)
addToTuples ("float", 3.0)
addToTuples ("string", "foo")
addToTuples ("tuple", (3, "int"))
addToTuples ("option", Some 1)

tuples
|> Seq.iter (fun (s, x) ->
    printf "String: %s, Value: " s
    match x with
    | :? int as i -> printfn "%d" i
    | :? float as d -> printfn "%f" d
    | :? string as s -> printfn "%s" s
    | :? (int * string) as t -> let x, y = t in printfn "(%d, %s)" x y
    | _ -> printfn "{%A}"  x
)

答案 1 :(得分:2)

当您致电addToMyTupleList<'a>时,编译器会静态地知道'a的具体类型(即您正在调用addToMyTupleList<int>addToMyTupleList<float>等)。相比之下,当您尝试在Seq.iter内进行拆箱时,您希望根据参数的运行时类型确定'a,而不是{&#39}。这是F#型系统的工作原理。

在我看来,你有几个选择:

  1. 使用类型测试,如Daniel建议的那样。
  2. 不是将原始值存储在列表中,而是存储您要生成的输出(即,在您放置内容时使用string list来调用sprintf。< / LI>
  3. 使用您在列表中存储的类型更准确一些。对∃'a.string * 'a类型进行编码(也就是说,它是由字符串和'a组成的对,对于某些未知的'a)并存储这些类型的列表。在像Haskell这样的语言中,这并不是太糟糕,但在F#中忠实地编码它是丑陋/令人困惑的:

    type ExPair<'x> =
        abstract Apply : string * 'a -> 'x
    type ExPair =
        abstract ForAll : ExPair<'x> -> 'x
    
    let pack p = { new ExPair with 
                        member __.ForAll<'x>(e:ExPair<'x>) : 'x = e.Apply p }
    
    let myList = [pack ("integer", 3)
                  pack ("float", 3.0)
                  pack ("string", "string")
                  pack ("tuple", (3, "integer"))]
    
    myList
    |> List.map (fun e ->
        e.ForAll { new ExPair<string> with member __.Apply(s,x) = sprintf "%s,%A" s x })