I am new to Dart and recently I've been reading some docs about Dart sound type system until I ran into a section called Use sound parameter types when overriding methods
It states that
The parameter of an overridden method must have either the same type or a supertype of the corresponding parameter in the superclass. Don’t “tighten” the parameter type by replacing the type with a subtype of the original parameter.
and shows me a block of code saying

My question is that, of course, by prohibiting parameter type "tighten" in an override method we can prevent
define a cat and send it after an alligator
class Animal {
chase(Animal a) {}
}
class Mouse extends Animal {}
class Alligator extends Animal {}
class CatA extends Animal {
@override
// error on this line
void chase(Mouse x) {} // It makes sense. Cat chases Mouse
}
class CatB extends Animal {
@override
// error on this line
void chase(Alligator x) {} // Cat chases Alligator ? You can't be serious!
}
But how about using a Object(witch is the supertype of almost all others) as its parameter type
class Cat extends Animal {
@override
void chase(Object x) { // Now this Cat can chase anything.....
}
}
void main() {
var cat = Cat();
cat.chase(Alligator()); // Again, we are trying to let a little pussy chase a terrifying beast!
}
// This piece of code works
What is it all about, this does not makes sense to me at all...Furthermore, what if I create a supercat that extends a Cat, which can indeed chase after a Alligator
class SuperCat extends Cat {
@override
// error on this line
void chase(Alligator x) { // I make a SuperCat chasing after a Alligator intentionally, but it doesn't work...
}
}
These things above are really blowing my minds off, do I get it wrong in some way or is there anything more under the hood that makes it this way?
Thanks to @jamesdlin and @Abion47, I could finally figure out most of the puzzles, but there is still one more problem to solve. As @jamesdlin mentioned, contract of the base class method must be honored when overriding a method. Taking Animal and Cat for example, Animal.chase makes a contract saying chase must be compatible to accept any Animal, no matter it is Alligator or
Mouse, but does this contract also make a restrction that chase must not be able to accept any other Object excepts for Animal?(It is natural to think about it because you can't pass a casual Object parameter into Animal.chase) And if it does, why does Dart allow widening Cat.chase parameter type from Animal to Object? Doesn't it violate the contract made by Animal.chase?
The answer referenced by jamesdlin has the details of why this isn't allowed, but I'll try to give a nutshell version.
abstract class Animal {
void chase(Animal a) { ... }
}
class Mouse extends Animal {}
class Alligator extends Animal {}
class Cat extends Animal {
@override
void chase(Mouse x) { ... } // No-no
}
In theory, this would be fine, since Mouse extends Animal and Cat is just restricting what kind of animal can be passed to chase.
But think about what happens with polymorphism. The hard rule is that any classes that extend and override a base class must be compatible with that base class. That ensures that, even if the function is called on a handle that is typed to the base class, it is guaranteed that the call will go to the correct function definition in the overriding class.
This is what makes abstract classes possible - if there was an implementation of a function in a derived class that wasn't compatible with the same function in the base class, it would be impossible to treat instances of inherited classes as though they were instances of the base class.
For example, take the example from the article but instead of explicitly creating a Cat, have it be some randomly generated animal:
Animal a = RandomAnimalFactory.create();
a.chase(Alligator());
What is the exact type of a? You have no way of knowing. All you know is that it is some subtype of Animal.
Now look at the call to a.chase(Alligator()). Would that succeed? Well, yes, because Animal.chase requires an Animal and Alligator is an Animal. No problems there.
But imagine if a subclass could restrict the type of that parameter, e.g. if Cat could restrict it to Mouse. Now it's unclear whether that call would succeed because even though you know that a is an Animal, you don't know what kind of Animal. What if it's a type that makes Alligator no longer a valid parameter? In order to know, the type system would have to unpack the declared type of the variable to examine the actual type, and only then would it know whether the call would succeed. This flies in the face of inheritance, where a type that extends a base type can be interchangeable with any of its sibling types.
All of a sudden, what used to be a simple matter has become extremely complex. Not only does it make the implementation of an OOP-based type system astronomically more complicated, it completely undermines the point of inheritance.
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