[英]F# and automatic upcasting: sometimes it does, sometimes it doesn't, what is the rationale behind that?
最近我在 F# 中嘗試了自動向上轉換的可能性,我知道 F# 是強制明確的,但是我注意到在某些情況下實際上存在向上轉換。
下面是一些代碼,顯示了向上轉換工作的情況和不工作的情況:
type NameValue(name: string, value: obj) = class end
type GenNameValue<'T>(name: string, value: 'T) = class end
type MoreNameValue<'Name, 'Value>(name: 'Name, value: 'Value) = class end
let takeObjList (source: obj list) = printfn "%A" source
let takeNameValueList (source: NameValue list) = printfn "%A" source
let takeGenNameValueList (source: GenNameValue<obj> list) = printfn "%A" source
let takeMoreNameValueList (source: MoreNameValue<string, obj> list) = printfn "%A" source
let takeAnonymousList (source: {| Name: string; Value: obj |} list) = printfn "%A" source
let takeStructAnonymousList (source: struct {| Name: string; Value: obj |} list) = printfn "%A" source
let takeTupleList (source: (string * obj) list) = printfn "%A" source
let takeStructTupleList (source: struct (string * obj) list) = printfn "%A" source
let takeValueTuples (source: ValueTuple<string, obj> list) = printfn "%A" source
let takeRefTuples (source: Tuple<string, obj> list) = printfn "%A" source
// Implicit upcasting to obj => works
takeObjList [42; "middle"; 16us]
takeNameValueList [NameValue("a", "c"); NameValue("b", 42)]
takeGenNameValueList [GenNameValue<obj>("a", "c"); GenNameValue<obj>("a", 42)]
takeMoreNameValueList [MoreNameValue<string, obj>("a", "c"); MoreNameValue<string, obj>("a", 42)]
takeAnonymousList [{| Name = "a"; Value = "c" |}; {| Name = "b"; Value = 42 |}]
takeStructAnonymousList [{| Name = "a"; Value = "c" |}; {| Name = "b"; Value = 42 |}]
// Implicit upcasting to obj => doesn't work
// [FS0001] This expression was expected to have type 'obj' but here has type 'string'
// [FS0001] This expression was expected to have type 'obj' but here has type 'int'
takeTupleList [("a", "c"); ("b", 42)]
takeStructTupleList [("a", "c"); ("b", 42)]
takeValueTuples [ValueTuple.Create("a", "c"); ValueTuple.Create("a", 42)]
takeRefTuples [Tuple.Create("a", "c"); Tuple.Create("a", 42)]
此外,我還注意到向上轉換元組單獨工作但當它是一個集合時立即失敗:
let build1 a b: (string * obj) list = [a; b]
let build2 a: (string * obj) list = a
// Does work
build1 ("a", "c") ("a", 42) |> ignore
// Does not work
build2 [("a", "c"); ("a", 42)] |> ignore
我想知道背后的原理。 恕我直言,它看起來有點不一致,在某些情況下你可以,在另一些情況下你不能。 我可能錯過了一些東西,我想知道是什么。
C# 具有隱式協變和逆變。 這使您可以自動將IEnumerable<string>
強制轉換為IEnumerable<object>
,或者將Action<object>
與int
一起使用。
F# 也沒有。 在這里投票! .
所以你不能寫:
[1; 2; 3] :> obj list //compile error
您看到的 rest 是類型推斷,試圖選擇最佳匹配類型。
let printlist (list: obj list) =
list |> List.iter(printfn "%A")
printlist [1; 2; 3] //compiles - literals will be inferred using type annotation
let intlist = [1; 2; 3]
printlist intlist //this will not compile
請注意,所有失敗案例都是針對非同質 collections 或協變 collections。
目前,F# 有靈活的類型,它本質上是語法糖:
#T = 'a where 'a :> T
它讓你有有限的協方差。
let printlist (list: #obj list) =
list |> List.iter(printfn "%A")
let intlist = [1; 2; 3]
printlist intlist //compiles
我相信其他人可以提供更多背景。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.