[Prev][Next][Index][Thread]

Re: Tim Sweeney on Programming Languages



>>>>> "Michael" == Michael Schuerig <schuerig@acm.org> writes:

    Michael> At 12:15 Uhr +0100 07.02.2000, Maarten Hazewinkel wrote:
    >> In article <1e5j7vy.1wbl2xsdh8on8N%schuerig@acm.org>,
    >> schuerig@acm.org (Michael Schuerig) wrote:

    >> >So, he wants to express abstractly the relationship among a
    >> bunch of >classes. This is exactly what he can get with
    >> interfaces and factories.
    >> 
    >> Yes, you can fabricate this kind of thing with interfaces and
    >> factories.  [..]

    Michael> [..] The mechanism
    Michael> that Sweeney proposes is not much better than interfaces
    Michael> and factories. The point is that both require
    Michael> forethought. [..]

Virtual classes are not just a convenience construct which may be 
simulated with factories.

Factories may stand in for classes for one particular purpose (namely
object creation) but not for others (such as inheritance or type
declarations).
Consider, for example, the following class family (using a Java-ish
syntax for familiarity, assuming virtual classes; `public'/`private'
omitted for clarity; no interfaces for brevity):

  class Graph {
      virtual class Node = {
          void attach(Edge E) { .. }
      }
      virtual class Edge = { .. }
  }

We may create a subfamily like this (we declare an `extended' version
of the virtual class Edge, which is basically what virtual classes are
for):

  class WeightedGraph extends Graph {
      extended virtual class Edge = { int weight; }
  }

The `attach' method of WeightedGraph.Node accepts an argument of
type WeightedGraph.Edge (the argument to `attach' in Graph.Node is of
type Graph.Edge which does not have a `weight').  The crucial point is
that we do not have to redeclare Node in WeightedGraph in order to
achieve this change of Node.

In any language where static type analysis plays a role, it is
essential to get this "automatic update of types" effect, since it
allows us to change just a few things in a family (or framework) of
classes, and then automatically reestablish the type relations within
the family.  As Tim Sweenie puts it:

  "...Stop for a second and ponder the power of such a concept -- with
  about four lines of code, you've sub-classed a 150,000 line game
  engine and added a new feature that will propagate to several
  hundred classes in that framework."

The important word here is "propagate".  To illustrate the difference
between subclassing an ordinary class, and specializing a virtual
class in a framework, consider the simple situation where a number of
classes A..K are designed to work together (so A may use any of A..K
for creation of new objects, as types of arguments and fields etc).
The "universe" class U is used to hold the classes A..K, so that we
can use virtual factory methods:

  <U> {
      <A>  <B>  <C>  ...              // original family
      "factory methods"               // used to create A's, B's etc.
  }

Now let us create a subclass C' of C.  We also subclass U to U', in
order to be able to redefine factory methods, such that every creation
of a C object in U is replaced by the creation of a C' object in U'.

  <U'> extends <U> {
       <C'> extends <C>               // this is the desired change
       "C factory method"             // factory method overhead
       ...                            // more overhead, see below
  }

The problem is that this will only handle the changes associated with
object creation.  For every method in C or in a sibling class (A,B,..)
that takes an argument of type C or returns a result of type C, we
must redeclare that method and the enclosing sibling class (shown as
A',B',.. below).  

  <U'> extends <U> {
       <C'> extends <C>               // this is the desired change
       "C factory method"             // factory method overhead
       <A'> <B'> ..                   // sibling overhead
       ...                            // still not enough
  }

But this is still not enough.  For every field of type C we have a
more serious problem: There is no way we could redeclare the type of a
field in a language like Java (and it would not be correct to override
it with a _new_ member of the same name and type pointer-to-C', "C'*",
as we might do in C++, because we would access the wrong member when
accessing a U' family polymorphically as a U family; note that Eiffel
actually supports a special case of virtual types using constructs
such as "myAttribute: like CURRENT", but since classes cannot be
nested in Eiffel it cannot be used for class family updates).

In other words, we cannot update the type of fields as we should when
a member of a family is subclassed.  This means that we cannot (in U'
or future extensions U'', U''', ...) write a method which uses the new
(C' specific) properties of a field which was originally declared as a
C in the U family.  In the Graph example above, this would mean that
we could not get access to the `weight' of an Edge.  (Of course we
might use unsafe casts to WeightedGraph.Edge, but that does not solve
the failure of the typesystem, it just highlights it).

To conclude, we cannot update the class family in a way that preserves
the type relations using inheritance and factories (i.e., the updated
version of a member of the class family does not _propagate_ to the
other members).  With virtual classes the update is trivial:

  <U> {
      <A>  <B>  <C>  ...           // original family of virtual cls.
  }

  <U'> extends <U> { <C> }         // declare a new version of C

In U', all the usages of C (in A, B, and everywhere) have been
automatically updated to refer to the new version of C (note that we
do not introduce a new name C', we just redefine C).

			      ----------

I hope this illustrates that the notion of virtual classes is not just
a convenience construct.  Of course, the above arguments do not apply
in languages where types are implicit and there is no static type
checking, but even here it makes a difference.  Consider the situation
where a number of classes inherit from virtual classes (marked with
"===="):

                           =======
                           |  A  |
                           =======
                              |
                ----------------------------
                |             |            |
             -------       =======      -------
             |  B  |       |  C  |      |  D  |
             -------       =======      -------
                              |
                        -------------
                        |           |
                     -------     -------
                     |  E  |     |  F  |
                     -------     -------


With a class family like this, we may create derived families where we
add functionality to one or both of the virtuals A and C.  Assume that
we create a variant family in which a method in C is overridden.  This
means that we have created a new family of classes in which that
method is overridded in C, E, and F, and we did this without
redeclaring the method in E or F, and without respecifying that E and
F are subclasses of C.  This is about the ability to create variant
class families with new functionality without affecting the original
class family, and without redeclaring the structure of the original
family in the declaration of the variant family.

In a sense, we create a class hierarchy with flexible internal nodes;
compare that with the traditional approach where a class hierarchy can
only be changed by adding new leaves to the tree (or DAG). 

			      ----------

<SHAMELESS-PLUG>
Oh, and by the way, virtual classes and their type analysis _can_ be
implemented; it has been used in practice since the early 80'ies in
the language BETA; inheritance from virtuals has been added recently,
in the language gbeta.
</SHAMELESS-PLUG>


  regards,

-- 
Erik Ernst                                    eernst@cs.auc.dk
Department of Computer Science, University of Aalborg, Denmark



Follow-Ups: References: