Reputation: 8997
Lately I played a bit with the possibilities of automatic upcasting in F#, I know that F# is forcing to be explicit, however I've noticed that upcasting actually kinda exists in some cases.
Below is some code showing cases where the upcasting is working and cases where it is not:
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)]
Additionally, I've also noticed that upcasting tuples individually works but failed instantly when it's a collection:
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
I would like to know the rationale behind. Imho it looks a bit inconsistent, in some cases you can, in some others you cannot. I'm probably missing something out and I would like to know what.
Upvotes: 3
Views: 55
Reputation: 12667
C# has implicit covariance and contravariance.
This lets you automatically cast IEnumerable<string>
to IEnumerable<object>
, or use an Action<object>
with an int
.
F# doesn't have either. Vote here!.
So you can't write:
[1; 2; 3] :> obj list //compile error
The rest of what you see is type inference trying to choose the best matching type.
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
Note that all of your fail cases are either for non-homogeneous collections or for covariant collections.
For now, F# has flexible types, which is essentially syntactic sugar for:
#T = 'a where 'a :> T
It lets you have limited covariance.
let printlist (list: #obj list) =
list |> List.iter(printfn "%A")
let intlist = [1; 2; 3]
printlist intlist //compiles
I'm sure someone else can provide more background.
Upvotes: 3