B.2 The concept of classes
If you are familiar with the class concepts of C++, you may be confused by Dylan's class model. In Dylan, all base classes are effectively virtual base classes, with "virtual" data members. When a class inherits another class more than once (because of multiple inheritance), only a single copy of that base class is included. Each of the multiple-inheritance paths can contribute to the implementation of the derived class. The Dylan class model favors this mix-in style of programming.
Here is an example of such a program, followed by the equivalent C++:
Mix-in example in Dylan. |
|---|
define class <window> (<object>)
slot width :: <integer>;
slot height :: <integer>;
end class <window>;
define class <border-window> (<window>)
slot border-width :: <integer>;
end class <border-window>;
define method width(window :: <border-window>)
next-method() - 2 * window.border-width;
end method width;
define method height(window :: <border-window>)
next-method() - 2 * window.border-width;
end method height;
define class <label-window> (<window>)
slot label-height :: <integer>;
slot label-text :: <string>;
end class <label-window>;
define method height(window :: <label-window>)
next-method() - window.label-height;
end method height;
define class <border-label-window>
(<border-window>, <label-window>, <window>)
end class <border-label-window>;
|
The example is a greatly simplified sketch of a computer-display windowing system, where a window may have a border (outline decoration), or a title (such as the title bar of a window), or both. (We omit any further detail, such as scroll bars.) One chore in such a system is to compute the available display area of a window from that window's overall size and from the sizes of the window's components.
Note that calling height on an instance of <border-label-window> will automatically perform the actions appropriate for a window with a border and a label. First, the method for <border-window> will be called, subtracting out the border width; when it calls next-method, to get the underlying window width, the method for <label-window> will be called, subtracting out the label height; finally, when it calls next-method, the method for getting the value of the height slot in the underlying window will be called.
This example is a classic one of the mix-in style — the full functionality of the <border-label-window> class is the result of the combination of the individual pieces of <border-window> and <label-window> functionality.
C++ equivalent of the mix-in example. |
|---|
class Window {
private:
int _width;
int _height;
public:
virtual int width() { return _width; }
virtual int height() { return _height; }
};
class BorderWindow : public virtual Window {
private:
int _border_width;
public:
virtual int border_width() { return _border_width; }
virtual int width();
virtual int height();
};
int BorderWindow::width() {
return Window::width() - 2 * border_width();
}
int BorderWindow::height() {
return Window::height() - 2 * border_width();
}
class LabelWindow : public virtual Window {
private:
int _label_height;
char *_label_text;
public:
virtual int label_height() { return _label_height; }
virtual char* label_text() { return _label_text; }
virtual int height();
};
int LabelWindow::height() {
return Window::height() - label_height();
}
class BorderLabelWindow :
public virtual BorderWindow,
public virtual LabelWindow,
public virtual Window {
public:
virtual int height();
};
// Have to generate "combined" method by hand in C++
int BorderLabelWindow::height() {
return Window::height() - 2 * border_width() - label_height();
}
|
It may be helpful for C++ programmers to consider that:
Dylan base classes are always virtual.
In Dylan, data members are accessed through virtual functions, so it is always possible to override access to a data member in a derived class, and to modify the returned value (or, by overriding the setter, to modify the value to be stored).
Dylan's
next-methodallows you to use automatic method combination when you are programming in a mix-in style.
Note that the C++ equivalent of the mix-in example is incomplete. It is intended only as a guide to how you can think of Dylan classes. In particular, we have not modeled the slot setter virtual functions that Dylan classes define automatically, and we have not gone into how instances of the classes are constructed. In Dylan, we would simply give init-keywords for each of the slots, and the automatically generated constructor would fill them in for any of the derived classes. In contrast, constructors for virtual base classes are a particularly difficult aspect of C++: They make it hard to model what is done in Dylan accurately. In general, the mix-in style of programming is more difficult to do in C++, because that language's support for it is quite limited.
Note also that the C++ code is provided only as a model of Dylan execution, so that you can understand the semantics of Dylan classes in C++ terms. Good Dylan compilers use library compilation, type inferencing, and partial evaluation to optimize out the overhead normally associated with virtual classes and virtual functions, while preserving the dynamic execution semantics.




