2014-04-25 47 views
2

我很想了解更多關於以下方面的信息。給定約束的輸入的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

當您撥打addToMyTupleList<'a>時,編譯器靜態地知道'a的具體類型(即,您正在調用addToMyTupleList<int>addToMyTupleList<float>等)。相比之下,當您嘗試在Seq.iter內進行拆箱時,您希望'a將根據參數的運行時類型確定,而不是F#類型系統的工作方式。

在我看來,你有幾種選擇:

  1. 使用類型的測試,如丹尼爾建議。
  2. 而是在列表中存儲的原始值,存儲要生成的輸出(即,使用string list,你叫sprintf爲你把事情英寸
  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 }) 
    
+0

謝謝!它可能不如Haskell版本簡潔,但我認爲這是一個非常有趣和優雅的解決方案,我喜歡看到新的方法來利用對象符號! –

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 
)