CarbonFlambe
CarbonFlambe

Reputation: 378

Subclass test without regard to type argument

If you run:

type Car<'T> () = class end
type Mercedes () =
    inherit Car<int> ()
let merc = Mercedes ()

and then run each of the following lines, you get the indicated results:

merc :? Mercedes        // true
box merc :? Mercedes    // true
merc :? Car<int>        // error FS0193: types not compatible
box merc :? Car<int>    // true
merc :? Car<_>          // error FS0193: types not compatible
box merc :? Car<_>      // false
  1. See the first four cases. Why do you need to box merc for the test against Car<int>, but not for the test against Mercedes?
  2. In the last two cases I'm trying to find something that'll return true because merc is a Car, without regard to the type argument. Is there such a thing?

Upvotes: 1

Views: 58

Answers (2)

Romain Deneau
Romain Deneau

Reputation: 3061

To answer your first question:

Why do you need to box merc for the test against Car<int>, but not for the test against Mercedes?

From the docs, the type test operator :?

Returns true if the value matches the specified type (including if it is a subtype); otherwise, returns false

Line 4 box merc :? Car<int> succeeds because box merc rises the type in the hierarchy up to object, the highest type.

Line 3 merc :? Car<int> should have return false because Mercedes > Car<int>. Instead, it's not compiling which is a behavior not documented but also not surprising for me as we always know that it cannot be true. Notice that it could have been just a warning, like in C# when we writes if (true) ....

But it's surprising why this behavior is not applied for line 1 merc :? Mercedes. It should fail to compile for the same reason of "obviousness". Instead, it's just a warning FS0067: This type test or cast of a base class into a derived class will always succeed 🤔

👉 Conclusion: the type test operator is really designed just as a downcast test operator. Using it in other cases like to check upcast can be misleading (at compile time) but still safe at runtime.

Upvotes: 0

Tomas Petricek
Tomas Petricek

Reputation: 243051

To answer your second question, there is no built-in operator for testing whether a value inherits from a class regardless of a type argument. You can check this using reflection by getting the base type of Mercedes and comparing its generic type definition with the generic type definition of Car<_>:

merc.GetType().BaseType.GetGenericTypeDefinition() = typedefof<Car<_>>

In practice, it may be much easier to introduce a non-generic base class though:

type Car() = class end
type Car<'T> () = 
    inherit Car()
type Mercedes () =
    inherit Car<int> ()

let merc = Mercedes ()
box merc :? Car

To answer your first question, I think the compiler is giving you a hint that the operation is not useful because it will always succeed - so there is no point checking this using :?.

If you instead have a value that has a static type of Car<int> and you want to check whether it is Mercedes, this is alowed, because that is an interesting question to ask:

Car<int>() :? Mercedes

But checking Car<obj>() :? Mercedes is not allowed, because this is statically known to be false.

Upvotes: 2

Related Questions