Reading example from typescript manual:
class Animal {
name:string;
constructor(theName: string) { this.name = theName; }
move(meters: number = 0) {
alert(this.name + " moved " + meters + "m.");
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(meters = 5) {
alert("Slithering...");
super.move(meters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(meters = 45) {
alert("Galloping...");
super.move(meters);
}
}
var sam = new Snake("Sammy the Python");
var tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
The question is about the line var tom: Animal = new Horse("Tommy the Palomino");:
As I understand tom is an Animal with properties of a Horse. Is that right?
What is the point to make it this way? Not to declare as var tom: Horse = ...?
Having just one version to give him a chance to degrade/change/evolve to a Snake or any other Animal. Am I right?
...or maybe it is just a typo?
In the example above, Animal is a superclass (also called base class or parent class) of both Horse and Snake. Correspondingly, Horse and Snake are subclasses (derived classes) of Animal.
When you declare the subclasses:
class Snake extends Animal
...
class Horse extends Animal
You're telling the compiler that any every Snake and every Horse is in fact, an Animal as well. This makes Animal the broader category in the "world" of the program. Snake and Horse will inherit the properties of Animal, but they can change them (and/or add a few of their own) to be more specialized.
tom's declaration tells the compiler that the variable will accept any Animal. As we saw earlier, a Horse is an Animal, so the compiler lets it pass.
Hence, they're illustrating the fact that whenever a member of the superclass is expected in any expression, one member of any of its subclasses is acceptable. This is called covariance.
In the most literal sense, there is no evolving or devolving. The line
tom: Animal = new Horse("Tommy the Palomino");
first causes a new Horse object to be created. The object is then assigned to the variable tom, but this assignment does not change the properties of the object. If you ran the example, you'd see that the call horse.move() actually calls the Horse version of the method move, which would report that "Tommy the Palomino moved 45m".
The only discernible side effect of assigning a Horse to an Animal is that the variable, being of the most general type, wouldn't know of any specialized properties of Horse. It only knows what all Animals have in common. Let's say Horse was declared like this:
class Horse extends Animal {
constructor(name: string) { super(name); }
move(meters = 45) {
//...
}
swat_fly() { /* ... */ }
}
You wouldn't be able to call tom.swat_fly(). If you wanted to, you either need to typecast tom (like this: (<Horse>tom).swat_fly()) or declare it as a Horse instead of as an Animal. But I reiterate: the object's properties are not changed to the superclass's.
So no, it's not a typo :)
Related
Background
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
What I am confused of
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?
Update
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.
Mutative covariant containers are unsound.
For example, and using no language in particular,
interface Pet;
class Cat extends Pet { meow(); }
class Dog extends Pet { woof(); }
class Box[T] {
value: T;
}
var dog: Box[Dog] = new Box(new Dog);
var pet: Box[Pet] = dog;
pet.value = new Cat;
dog.woof(); // uh oh!
Is there an analogous unsoundness with contravariant types?
My intuition is no. Loosely, the above unsoundness comes from the fact that a Box[T] "contains" a reference to a T, but a contravariant type like Predicate[T] does not "contain" anything at all.
However, I can't find much on the subjet.
I know this question has been asked before here. But the answers there do not satisfy my doubt.
I was told that they prevent mix-up of class types, the code below shows that they're not mixed up at all.
So, it shouldn't matter right?
Classes:
package Practice
abstract class Animal {
def name: String
}
abstract class Pet extends Animal {}
class Cat extends Pet {
override def name: String = "Cat"
}
class Dog extends Pet {
override def name: String = "Dog"
}
Here is the real confusion:
//Class with Upper Bound
class PetContainer[P <: Pet](p: P) {
def pet: P = p
}
//Class with Subtyping(Or Upcasting, I think they're the same)
class SimplePetContainer(p: Pet){
def pet: Pet = p
}
Driver Code:
val CatContainer: PetContainer[Cat] = new PetContainer[Cat](new Cat)
val DogContainer: SimplePetContainer = new SimplePetContainer(new Dog
println(CatContainer.pet.getClass)
println(DogContainer.pet.getClass)
Output:
class Practice.Cat
class Practice.Dog
//Practice was the package
Like I mentioned before, the classes are preserved.
So my question is, What advantage does Upper Bound have on Subtyping?
With your CatContainer, you know that CatContainer.pet is a Cat at compile-time. Meaning that the compiler also knows that. So you can say
CatContainer.pet.meow()
For the SimplePetContainer you do not have static type information about the pet inside anymore.
Like I mentioned before, the classes are preserved.
At runtime, the pet of course still knows its type (well, almost, it knows its class, which in your case would have been enough, any extra type information such as the generic types of that class has been erased).
But the variable DogContainer.pet lacks information about what sort of Pet it contains.
I was told that they prevent mix-up of class types
The compiler won't stop you from writing
val DogContainer = new SimplePetContainer(new Cat())
but it will reject this
val DogContainer = new PetContainer[Dog](new Cat())
What are the differences among these ways of defining Animal:
First way:
trait Animal {
def color: String
}
Second way:
trait Animal {
val color: String
}
Third way:
abstract class Animal(color: String) {}
Dog is a subclass of Animal. Consider the first way and the second way of defining Animal, what are the differences among the following ways of defining Dog:
First way:
case class Dog() extends Animal {
override def color:String = "black"
}
Second way:
case class Dog() extends Animal {
val color = "black"
}
Third way:
case class Dog(color: String) extends Animal {}
Forth way:
case class Dog(override val color: String) extends Animal(color) {}
Whoa, a lot to be answered here.
Regarding your first question, if you use a val then all subclasses must also use val. If you use def, subclasses can implement it either using def, val or lazy val. If color is a stable, immutable value, then declaring it as "val" in the trait makes sense since it imposes that all implementations in concrete subclasses will also be immutable.
The third way makes color only available in the constructor body and not visible from the outside. However, if you wrote
abstract class Animal(val color: String) {}
then it would be the same as the second way, only using abstract class instead of the trait. You could create a new animal and access its color attribute.
Regarding dog, defining color as def means that it will be computed every time it is invoked (i.e. when someone tries to access myDog.color). Defining it as val means that it will be an immutable value calculated once and for all when dog object is created. If it were a lazy val, then it would be calculated once and for all, but not when the dog is created, but when its color attribute is invoked (the calculation is postponed until the point of usage, hence the "lazy").
As I said above, if the Animal trait uses a val, then the Dog must also use a val. If Animal uses a def, then Dog can implement that as a def, val or lazy val.
Third way of writing a Dog is simply providing a parameter in case of writing an Animal with a class parameter (which was also third way in animal case). As I said earlier, in this case you cannot access the color attribute from the outside (that is, have val myDog = new Dog("blue") and access myDog.color).
Fourth way of writing a dog is implementing the Animal in case it was written in the way I have shown you above in the code (with using a val keyword). Now the color attribute will be visible. Override is not mandatory since you are implementing an abstract method, not overriding a concrete method, but you can leave it if you like (this way compiler will warn you if you, say, misspell "color" or someone removes the color from Animal class).
Perhaps this article can help too.
Please let me know how to make the following bit of code work as intended. The problem is that the Scala compiler doesn't understand that my factory is returning a concrete class, so my object can't be used later. Can TypeTags or type parameters help? Or do I need to refactor the code some other way? I'm (obviously) new to Scala.
trait Animal
trait DomesticatedAnimal extends Animal
trait Pet extends DomesticatedAnimal {var name: String = _}
class Wolf extends Animal
class Cow extends DomesticatedAnimal
class Dog extends Pet
object Animal {
def apply(aType: String) = {
aType match {
case "wolf" => new Wolf
case "cow" => new Cow
case "dog" => new Dog
}
}
}
def name(a: Pet, name: String) {
a.name = name
println(a +"'s name is: " + a.name)
}
val d = Animal("dog")
name(d, "fred")
The last line of code fails because the compiler thinks d is an Animal, not a Dog.
You should create companion objects with apply method for each subclass of Animal instaed of Animal trait. Also, it is considered a bad practice to use mutable field like you did with name.
You can do that, without changing anything else :
val d = Animal("dog").asInstanceOf[Dog] //> d : Test.Dog = Test$$anonfun$main$1$Dog$1#1030dda
name(d, "fred") //> Test$$anonfun$main$1$Dog$1#1030dda's name is: fred
But, i don't think it's a very good idea...
I don't want to sound rude but the compiler is right about assuming that d is an Animal because that's what the Animal.apply method returns.
As already pointed out you could force the type of d with an explicit cast but it simply wouldn't be type safe. It would be leveraging your knowledge about the method implementation as a programmer, which will eventually become a source of bugs as your codebase grows and you possibly change previous code in unexpected ways.
If you need to call a Pet method then you would better use a factory method that creates Pet objects, or at least check the object type before doing the type cast, using
if (d.isInstanceOf[Pet]) name(d.asInstanceOf[Pet], "Fred")
Or better still, using pattern matching
val d = Animal("dog")
d match {
case p: Pet => name(p, "fred")
case _ =>
}