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?

2 answers

  • answered 2022-01-25 10:11 Tomas Petricek

    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.

  • answered 2022-01-26 06:34 Romain Deneau

    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.

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum