Consider this:
module Module1 =
type A() = class end
type B() = inherit A()
type C() = inherit A()
let f x = if x > 0 then new B() else new C()
The last line yields an error about type B being expected, but type C being found instead.
Ok, I can pretend to understand that: the compiler doesn’t know which common base to infer in case there are many.
But guess what? Even when I specify the function type, it still doesn’t work:
let f x : A = if x > 0 then new B() else new C()
Now this gives me two errors: “A expected, B found” and “A expected, C found“.
WTF? Why can’t it see that both B and C are implicitly convertible to A?
Yes, I do know that I could use upcast, like so:
let f x : A = if x > 0 then upcast new B() else upcast new C()
But guess what (again)? upcast only works in the presence of the explicit function type declaration!
In other words, this:
let f x = if x > 0 then upcast new B() else upcast new C()
still gives an error.
WTF?! Do I really have to add 50% of noise to my program just to help the compiler out?
What’s with all that hype about F# code being clean and noiseless?
Somehow it feels like this cannot be true.
So the question is: am I missing something? How do I make this both compact and working?
Type inference and subtyping do not play well together, as Carsten’s links discuss to some extent. It sounds like you are unhappy with F#’s approach and would prefer it if
were implicitly treated more like
with the compiler additionally inferring
'ato be the least upper bound in the type hierarchy based on the types that would otherwise be inferred fore1ande2.It might be technically possible to do this, and I can’t definitively speak to why F# doesn’t work this way, but here’s a guess: if
ifstatements behaved this way then it would never be an error to have different types in theifandelsebranches, since they could always be unified by implicitly upcasting them toobj. However, in practice this is almost always a programmer error – you almost always want the types to be the same (e.g. if I return a character from one branch and a string from the other, I probably meant to return strings from both, notobj). By implicitly upcasting, you would merely make the presence of these errors harder to find.Furthermore, it’s relatively rare in F# to deal with complicated inheritance hierarchies, except perhaps when interoperating with other .NET code. As a result, this is a very minor limitation in practice. If you’re looking for a syntactically shorter solution than
upcast, you might try:> _, which will work as long as there is something to constrain the type (either an annotation on the overall result, or a specific cast on one of the branches).