Winter 91 - THE POWER OF MACINTOSH COMMON LISP
THE POWER OF MACINTOSH COMMON LISP
RUBEN KLEIMAN
Macintosh Common Lisp (MCL) is a powerful implementation of the Lisp language as
well as a dynamic development environment. This article describes major aspects of
the MCL language and environment. It provides essential information for non-Lisp
programmers who are unaware of the power of this language or of the MCL
development environment, as well as for Lisp programmers who are unaware of MCL
features or performance.
As the price of memory plummets and powerful computers become as cheap as sand,
developers are beginning to look afresh at the positive aspects of dynamic
programming environments, like Lisp and Smalltalk, that once seemed too slow and
memory-hungry. These environments offer the proven ability to generate and run
large-scale applications, easily access the toolbox, and call MPW C, Pascal, or
Assembler programs. With comprehensive and elegant class libraries for defining
user interfaces, these environments promise to significantly improve programmer
productivity over traditional languages.
Many Lisp environments are available for the Macintosh. We'll focus here on what one
particular dynamic programming environment, Apple's own Macintosh Common Lisp
(MCL), has to offer. We'll compare it to the programming environment provided by
MPW in conjunction with MacApp, Apple's object-oriented application framework
based on Pascal. We'll take a close look at its key advantages, and will illustrate them
with fragments from a sample program. The entire sample program plus a
step-by-step description of its development can be found on the Developer Essentials
disc that accompanies this issue.
WHAT IS MCL?
MCL is a powerful implementation of the Lisp language. (If you 're new to Lisp, take a
look at the sidebar "A Mini Lisp Tutorial" for a quick overview of how it differs from
Pascal.) MCL provides full compatibility with the Common Lisp standard, an extensive
object-oriented system, and a rapid prototyping development environment.
MCL 2.0 supports Common Lisp and the Common Lisp Object System (CLOS). This
extension of the Common Lisp standard offers an object-oriented programming
paradigm for Lisp, within which MCL implements a class library for developing user
interfaces. The MCL environment includes a syntax-oriented text editor for Lisp; a
direct way to navigate through sources; a tracer, stepper, and backtracer; and the
ability to disassemble code just in case you want to shave off a microsecond.
The key advantages of MCL are its interactivity, the inherent power of symbolic
processing in Lisp, the overall consistency of its object library, and its abstraction
away from the Macintosh event-loop style of programming. We'll take a closer look at
these advantages as we compare MCL with MacApp/MPW.
A COMPARISON OF MCL AND MACAPP/MPW
MCL and MacApp/MPW are both object-oriented programming environments available
from Apple. We'll compare four different aspects of these environments:
• Their language bases
• Their class libraries and event systems
• Their strong points as development environments
• Their size and performance specifications
LANGUAGE BASES
MacApp is based on ObjectPascal, a set of object-oriented extensions to Pascal
somewhat on a par with the C++ extensions to C. MCL is based on the Common Lisp
standard (ANSI X3J13 Committee), which includes the Common Lisp Object System
(CLOS), an object-oriented extension to Lisp. Table 1 gives an overview of what these
languages offer.
The most striking differences in the languages are (1) their syntax (described in the
sidebar "A Mini Lisp Tutorial"), (2) the ability of Common Lisp to deal with typeless
variables, and (3) Common Lisp's automatic garbage collection. Let's turn our
attention to the latter two differences.
Table 1 Features of ObjectPascal Versus Common Lisp
Feature ObjectPascal Common Lisp
Instance variables Yes Yes
Class variables No Yes
Multiple inheritance No Yes
Inheritance types One One standard,
user-redefinable
Method combination Not applicable Yes
Before/after methods No Yes
Methods on instances No Yes
Method discrimination On single argument On all arguments
Toolbox interface Yes Yes
Variable typing Required Optional
Garbage collection Manual Automatic
Foreign language interface Yes (MPW object files) Yes (MPW object files)
Error handling Yes Yes
In Lisp, you need not declare a variable's type. You can assign to a Lisp variable any
type of object, or many types of objects at different times, within a lexical scope. The
type information is associated with the data objects themselves rather than with the
variables. However, declaration statements are available for optimal compilation. A
common practice is not to type variables until the program is thoroughly debugged,
and then to use typing only in the most crucial parts of the code. For better
performance, you can require the run-time system to forego type checking.
Common Lisp does automatic garbage collection of inaccessible values (for example,
objects, strings, arrays)--that is, values that are implicitly deallocated. A key
advantage of this is simplification of your code. For example, the following statement
allocates an instance of the classWindow and binds it to the variablemyWindow: (setq myWindow (make-instance 'Window))
If thereafter you set myWindow to a different value, say,
(setq myWindow (make-instance 'Dialog))
Common Lisp will free up the space occupied by the Window instance (unless, of course, you've bound it to a different variable or the window is still open). Much of the
power of Lisp derives from the ability to implicitly allocate and deallocate,as well as
to easily access, simple data structures like lists, or complex objects. In contrast,
MacApp requires explicit method calls to allocate, initialize, and deallocate objects. In
both cases, you must explicitly dispose of space that you've allocated from the
Macintosh heap via Memory Manager calls. However, Common Lisp allocates space for
its own objects and other data structures in its own heap area managed by the garbage
collector.
We can compare the key features of ObjectPascal and Common Lisp object systems by
inspecting the code needed to define two classes of objects, Beeper and LongBeeper.
These classes have a BeepMe method that causes them to beep a number of times
specified by an instance variable.LongBeeper inherits from Beeper. Beeper makes
three short beeps, and LongBeepermakes four long beeps followed by the number of
short beeps Beeper makes. In the ObjectPascal code, we abrogate specifications
otherwise required by the ObjectPascal compiler that don't concern us.
Here's the ObjectPascal code:
TYPE
TBeeper = OBJECT
fBeeps: integer;
PROCEDURE TBeeper.IBeeper;
PROCEDURE TBeeper.BeepMe;
END;
TLongBeeper = OBJECT(TBeeper)
fLongBeeps: integer;
PROCEDURE TLongBeeper.IBeeper;
PROCEDURE TLongBeeper.BeepMe;
END;
PROCEDURE TBeeper.IBeeper;
BEGIN
SELF.fBeeps := 3;
END;
PROCEDURE TLongBeeper.IBeeper;
BEGIN
INHERITED IBeeper;
SELF.fLongBeeps := 4;
END;
PROCEDURE TBeeper.BeepMe;
VAR Count: integer;
BEGIN
For Count := 1 to SELF.fBeeps do
SysBeep(30);
END;
PROCEDURE TLongBeeper.BeepMe;
VAR Count: integer;
BEGIN
For Count := 1 to SELF.fLongBeeps do
SysBeep(120);
INHERITED BeepMe;
END;
{A function that uses the LongBeeper class}
FUNCTION UseBeeper;
VAR myBeeper: TLongBeeper;
BEGIN
NEW(myBeeper);
FailNil(myBeeper);
myBeeper.ILongBeeper;
myBeeper.BeepMe;
UseBeeper := myBeeper;
END;
The same sequence in Common Lisp looks like this:
(defclass Beeper ()
((Beeps :initform 3)))
(defclass LongBeeper (Beeper)
((LongBeeps :initform 4)))
(defmethod BeepMe ((me Beeper))
(dotimes (count (slot-value me 'Beeps))
(_SysBeep :word 30)))
(defmethod BeepMe ((me LongBeeper))
(dotimes (count (slot-value me 'LongBeeps))
(_SysBeep :word 120))
(call-next-method))
;;; A function that uses the LongBeeper
(defun UseBeeper ()
(let ((myBeeper (make-instance 'LongBeeper)))
(BeepMe myBeeper)
myBeeper))
Although Common Lisp object system may at first sight seem to have more features
than any particular programmer would need, in fact these capabilities are normally
used by Lisp programmers.
Multiple inheritance is an instructive example. If you are trying to define classes with
complementary behavior, multiple inheritance is the most elegant and economical
solution. For example, you can define two classes called ReadStream and
WriteStream that support read-only and write-only behavior for streams,
respectively. This gives you the option of basing a class of read-write streams on
inheritance from these classes:
(defclass ioStream (ReadStream WriteStream) ())
Since ReadStream and WriteStream are independent, you can also define a class of
windows that act like write-only streams by inheriting from both the Window and WriteStream classes:
(defclass StreamWindow (Window WriteStream) ())
Using single inheritance to define ioStream and Window would result in redundant and unmodular code--one of the problems object-oriented programming tries to solve.
As you use multiple inheritance more seriously, however, you may have to deal with
cases where you inherit multiple definitions of the same method. From the viewpoint
of your class's semantics, you will probably want to do one of the following: (1)
inherit all or some of the methods in any order or in a specific order, or (2) inherit
none of the methods. Common Lisp allows you to deal with any of these possibilities.
For example, to avoid inheriting a method, you simply redefine the method for the
class you are defining without making a call tocall-next-method. The latter is a
generalization of ObjectPascal's INHERITED (compare above the BeepMe methods for
the LongBeeper class in ObjectPascal and Common Lisp). Method combination is a
feature that enables you to specify the order in which methods of a given name will be
invoked.
"Before" and "after" methods enable you to specify behavior that should execute just
before or after your method is invoked. This provides you with flexibility in method
combination in subtle cases because the before and after methods are not embedded in
the code of the primary method. But more interesting is the manner in which Common
Lisp methods are dispatched. Whereas most object- oriented systems dispatch on the
class of the first argument, Common Lisp bases the method dispatch on the class of each
argument passed to a method call. One example of a case in which you may want to
dispatch on two arguments is when you have a Print method that can print on a variety of media. If you have a class Document that you want to be able to print into aColorLaser stream or into an ImageWriter stream, you can define Print as follows:
(defmethod Print ((thingToPrint Document) (stream ColorLaser))
;; Code to print to a ColorLaser goes here
)
(defmethod Print ((thingToPrint Document) (stream ImageWriter))
;; Code to print to an ImageWriter goes here
)
This generalizes object-oriented programming's idea that you shouldn't have to
special-case your methods: the appropriate method will be called by the system on the
basis of the type of all passed arguments. In particular, if the second argument
(stream) is a ColorLaser, then the first method above will be called; if the stream is
an ImageWriter, then the second method will be called. The alternative would be to
check what kind of stream you are writing to within a monolithic Print method. CLASS LIBRARIES AND EVENT SYSTEMS
Class libraries, which are provided with the language (some third parties sell
alternative libraries or extensions), impose a model of how Macintosh events are
handled, what kinds of Macintosh components (such as menus, dialog boxes) are
available, and how these interact.
Both MacApp and MCL offer a set of classes to easily instantiate menu bars, menus,
menu items, pull-down and pop-up menus, windows, dialogs, buttons, check boxes,