Genericity
We got a very short introduction to generic classes when we were looking at the formal generic part of class structure in Eiffel Classes . That discussion left to the imagination the motivation and benefits for creating generic classes.
You will see that most of the generic classes model containers for multiple items and at least one of their formal generic parameters represents the type of items that are to be stored in the container. Some generic classes, like LINKABLE
, care for only one instance of the type represented by their formal generic parameter.
Motivation
Imagine that a software producer is planning to build a class which would represent a list of things. Someone might ask "What kinds of things?" To which the producer would reply, "Just things. I want my list to be usable for all kinds of things."
Using the idea of polymorphic attachment that we learned in Inheritance , the producer could build such a class. It might have a query item
which would return the thing from the list to which a cursor currently points. It might have a command put
which would enter some new thing into the list.
What would be the type of item
? And what would be the type of the argument to put
?
If the producer wants the class to handle all kinds of things, then the answer must be class ANY
, the class from which all others inherit.
class
LIST_OF_THINGS
...
feature -- Access
item: ANY
-- The thing currently pointed to by cursor
...
feature -- Element change
put (new_item: ANY)
-- Add `new_item' at the end of the list
...
This will work, but has some definite disadvantages. Suppose you choose to use this class to maintain a list of cats in one of your classes. You might make this declaration:
my_cats: LIST_OF_THINGS
-- A list of my cats
Then you could add individual instances to the list:
fluffy, twinkie: CAT
...
my_cats.put (fluffy)
my_cats.put (twinkie)
One problem with this type of list is that the type system will not help you keep from doing something pathological like:
fluffy, twinkie: CAT
thor: PSYCHOTIC_HYDROPHOBIC_CAT_HATING_DOG
-- A very nasty dog
...
my_cats.put (fluffy)
my_cats.put (twinkie)
my_cats.put (thor)
Another problem is that to do any CAT
things with an item in the list, you must reattach it to a CAT
entity. The following is invalid.
my_cats.item.purr -- Is invalid
This is because "item" is type ANY
and although it may be currently attached to an instance of CAT
, the static typing system cannot guarantee that. So you must use an object test as we saw in the polymorphism example in Inheritance .
...
if attached my_cats.item as some_cat then
some_cat.purr
end
You can see that this type of list has its drawbacks. Of course you could build a LIST_OF_CATS
class in which item
and the argument for put
would be of type CAT
. This would let you purr
a cat without pulling it out of the list, and it would also prevent you from accidently letting old Thor in with the cats. But, every time you needed a list to hold a different type of object, you have to write a new class.
Indeed, this is how things are done in environments without facilities genericity.
What we would like to have is a way to produce the text of the list class once. Then only when we make declarations do we add the additional information about the particular types we want allowed in the list.
Basic Genericity
In Eiffel this is accomplished through generic classes. Generic classes are written relative not to a specific class but to a kind of phony class name called a formal generic parameter. With genericity, the LIST_OF_THINGS
class might become a class called LIST
which is a list of items of type G
. In class LIST
we would declare item
as type G, as well as the argument to put
.
class
LIST [G]
...
feature -- Access
item: G
-- The item currently pointed to by cursor
...
feature -- Element change
put (new_item: G)
-- Add `new_item' at the end of the list
...
We could declare feature my_cats
as a LIST
of items of type CAT
. By doing so we are providing CAT
as an "actual generic parameter" in the declaration. Then we are free to treat the features of LIST
as if the class name CAT
had been substituted for every occurrence of the formal generic parameter G
.
my_cats: LIST [CAT]
-- A list of my cats
fluffy, twinkie: CAT
...
my_cats.put (fluffy)
my_cats.put (twinkie)
...
my_cats.item.purr -- Valid now
The following would no longer be valid:
my_cats: LIST [CAT]
-- A list of my cats
thor: PSYCHOTIC_HYDROPHOBIC_CAT_HATING_DOG
...
my_cats.put (thor) -- Is invalid
Constrained Genericity
The generic class LIST
illustrated above is perfectly useful for making typed lists of any type of object. The features of the LIST
will not attempt to use the objects in the list in any way. Sometimes though, it is important for a class to be guaranteed more about the nature of the types that can be substituted for its formal generic parameter.
Take for example the case of a class called SORTED_LIST
. A SORTED_LIST
is a list, of course, but it is special in that it acts upon the elements that it holds to keep them in order.
A SORTED_LIST
needs to be able to order its elements. So, it must be able to apply queries to those elements to determine which should sort high than which. The elements themselves must respond to ordering operations.
If SORTED_LIST
were defined like we did LIST
class
SORTED_LIST [G]
there would be no guarantee that ordering operations, like "<" and ">" could be applied to all types that could be listed. An Eiffel facility called "constrained genericity" will solve this problemfor us. In the case of SORTED_LIST
, we would add to the formal generic part as follows.
class
SORTED_LIST [G -> COMPARABLE]
You may remember from Inheritance that if we make instances of a class comparable with each other, then we make the class inherit from COMPARABLE
and effect the feature "<".
Here, constrained genericity does two things for us.
- First, it states that any candidate for substitution for
G
must conform to classCOMPARABLE
. Typically this means it must inherit fromCOMPARABLE
. - Second, it allows, within the features of
SORTED_LIST
, the features ofCOMPARABLE
to be applied to any item which has a type ofG
.