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
if b then
e1
else
e2
were implicitly treated more like
if b then (e1 :> 'a) else (e2 :> 'a)
with the compiler additionally inferring 'a
to be the least upper bound in the type hierarchy based on the types that would otherwise be inferred for e1
and e2
.
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 if
statements behaved this way then it would never be an error to have different types in the if
and else
branches, since they could always be unified by implicitly upcasting them to obj
. 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, not obj
). 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).
there is a reason for all of this but to make it short: F# is more strong typed than C# so you have to tell where to cast to (see here):
let f x = if x > 0 then (new B() :> A) else (new C() :> A)
Here you can find further information: F# need for cast
And here is another great discussion on this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With