Issue with checking with an instance is the type of a generic type

So I converted a class from a non-generic to a generic. The problem was that I had code that checked whether or not an instance was of that type before the generic was added. For example:

instance is Alpha

However, Alpha was now generic:

class Alpha<T>: where T: SomeOtherClass {}

The problem was that after I converted the class to a generic, the compiler didn't complain about Alpha not being used as a generic in the type comparison. Furthermore, what I would expect to happen is that if an instance is any Alpha that instance is Alpha would be true. But that was not the case.

What actually happened was that that type check would return false when instance was a Alpha<SomeOtherClassSubclass>. I presume this was happening because for some reason when an explicit generic is not specified, their is a generic constraint, and it is a concrete type, that all uses of Alpha implicitly become Alpha<SomeOtherClass>.

Is this documented somewhere?

There’s a number of issues to unpack here.

First:

This is not correct. You do not indicate the context where the code you’ve excerpted is written, but if it is within the body of class Alpha<T>, then Alpha is a shorthand for Alpha<T>, not Alpha<SomeOtherClass>.

To put it concretely, if a user instantiates a value of type Alpha<SubclassOfSomeOtherClass>, then the implicit T is SubclassOfSomeOtherClass, not SomeOtherClass.

Second:

Not only is that not the behavior when you write instance is Foo, but in the general case where Foo<T, U, V> has arbitrary generic parameters, there exists no operation whatsoever which does what you describe.

That is because, if instance is XXX is true, then instance as! XXX succeeds and gives you a result with the static type XXX. However, there is no such thing as a “type Foo without generic parameters” that you can use on the right-hand side of an as! cast.

You would need another additional abstraction, such as a FooProtocol (which can now make use of primary associated types for parameterization) to accomplish something like what you describe.

Third:

With respect to the expectation, even if all of the above were not the case, it would be undesirable for refactoring a non-generic class to a generic class to behave how you describe.

Given Bar<T>, Bar<Int> and Bar<String> are distinct types. With a non-generic Bar, the query is Bar asks whether a given instance has the dynamic type of a specific concrete type Bar—a user may then want to compare two instances with == if the dynamic types match, for instance. It would be very alarming for the same statement, untouched, within the implementation of a refactored generic Bar to somehow lead to an illogical attempt to equate an instance of type Bar<Int> to an instance of type Bar<String>.

Fourth:

Generics allow you to reason about polymorphism using the static type system, while is and as! allow you to conditionalize behavior based on the dynamic type of instances.

There are sometimes—sometimes—good reasons to mix the two. However, as a general rule of thumb, if you are both the API author and user and you’ve used generics when you authored the API, but now you find that as the user you immediately want to abstract away the generic parameters because you need the dynamism, there is probably a deeper design problem.

Without knowing the specifics of why you refactored to make your class generic, based on the use case you’re describing cursorily here, I’d suggest that your original design was the more appropriate one.

1 Like