Outline
Other Material
In Programming languages:
In a statically typed programming language (Java or Pascal), for example, variables have declared typed -- fixed at compile time.
In a dynamically typed programming language (Smalltalk or CLOS), a variable is just a name. Types are associated with values, not variables. A variable can hold different types during the course of execution.
Static and Dynamically typed languages have existed as long as there have been programming languages. Arguments for and against:
Both arguments have some validity, and hence both types of languages will continue to exist in the future.
The addition of object-oriented ideas in a statically typed languages adds a new twist. Recall the argument for substitution: an instance of a child class should be allowed to be assigned to a variable of the parent class:
var pet : Mammal; fido : Dog; felice: Cat; begin pet := fido; // legal pet := felice; // legal fido := pet; // not legal!
In a statically typed language we say the class of the declaration is the static class for the variable, while the class of the value it currently holds is the dynamic class.
Most statically typed OO languages constrain the dynamic class to be a child class of the static class.
var pet : Mammal; fido : Dog begin pet := fido; // static class is Mammal, dynamic class is Dog end;
In a statically typed object-oriented language, the legality of a message is determined at compile time, based on the static class.
A message can produce a compile error, even if no run-time error could possibly arize:
class Mammal { } class Dog extends Mammal { void speak() { System.out.println("woof"); } } Mammal pet = new Dog; pet.speak(); // will generate error, Mammals don't speak
Polymorphism says we can assign a value from a child class to an instance of the parent class, but can this assignment then be reversed? Under what conditions?
var pet : Mammal; fido : Dog; felice : Cat; begin pet := fido; // legal fido := pet; // is this legal? end;
This is known as the problem of reverse polymorphism.
There are two specific problems associated with the question of reverse polymorphism.
In some languages mechanisms are provided to address these two problems together, while in other languages they are separated.
The task of reverse polymorphism is often encountered in connection with a collection of values - we have a list of items from the parent class (say a list of Mammals), and when we extract a value we need to know if it is a more specific type.
Generally occurs in languages with a single inheritance tree, where the only type we may have associated with a value is the class ``Object''.
Solving this problem generally requires values to have ``self knowledge'' of their own type. In some languages they do, in some languages values do not.
A case study examining one solution to the container class problem will
be presented in a later chapter.
Java, unfortunately, uses to term Container to mean a type of graphical
component, and not a collection class, as is common in most other languages.
Should the binding for information be associated with the static class of a variable or the dynamic class.
Alice holds a small Mammal - asks Bill ``does this animal give birth to live young''.
Static answer - All mammals give birth to live young - therefore yes.
What if the Mammal is a platypus? Dynamic answer - Platypus lay eggs, therefore no.
Even statically typed OOP languages can use dynamic binding. But may use static type to determine legality of operation.
In many languages dynamic binding is the default. If a child class overrides a method in the parent, using the same type signature, then the selected method will be determined by the dynamic type.
In other languages (C++, Delphi, C#) the programmer must indicate which methods are dynamically bound and which are statically type. In C#, for example, this is done using the virtual keyword.
class Animal { public: virtual void speak () { cout << "Animal Speak !\n"; } void reply () { cout << "Animal Reply !\n"; } }; class Dog : Animal { public: override void speak () { cout << "woof !\n"; } void reply () { cout << "woof again!\n"; } }; class Bird : Animal { public: virtual void speak () { cout << "tweet !\n"; } }; | Animal a; Dog b; b.speak(); woof ! a = b; a.speak(); woof ! Bird c; c.speak(); tweet ! a = c; a.speak(); tweet ! |
C++ is the most complex language. Not only must the programmer use the virtual keyword, but true polymorphism only occurs with pointer or reference variables.
class Animal { public: virtual void speak () { cout << "Animal Speak !\n"; } void reply () { cout << "Animal Reply !\n"; } }; class Dog : public Animal { public: virtual void speak () { cout << "woof !\n"; } void reply () { cout << "woof again!\n"; } }; class Bird : public Animal { public: virtual void speak () { cout << "tweet !\n"; } }; | Animal * a; Dog * b = new Dog(); b->speak(); woof ! a = b; a->speak(); woof ! Bird c = new Bird(); c->speak(); tweet ! a = c; a->speak(); tweet ! |
We will see an explanation for the curious C++ semantics when we discuss memory management in the next chapter.
Arguments concerning static versus dynamic binding mirror those concerning static versus dynamic typing.