August 91 - Macintosh Common Lisp and CLOS
Macintosh Common Lisp and CLOS
Jeffrey W. Stulin
This article is the first in a series of three on Macintosh Common Lisp (MCL) and
Common Lisp Object System (CLOS).
MCL is worth learning about, even for those of you who don't think you'll use
it-because it's likely that there will eventually be Macintosh object programming
environments and operating systems to come that are based upon technology strongly
influenced by Lisp.
This first article introduces the MCL series, and presents a basic Lisp tutorial. The
second article will discuss the MCL environment and tools, and introduce CLOS. The
third article will present an MCL version of the author's MacApp sample program
Game Of Life, and discuss how developing Life in MCL was different than developing it
in MacApp and Pascal.
Macintosh Common Lisp 2.0b1 (MCL) offers a refreshingly different approach to
software prototyping and development. In this series of three articles, I'll describe the
feel of development using MCL, and explore how MCL development can lead to a lower
"cost of innovation" than development using MPW and MacApp.
AN author's dilemma
The classic approach of showing a language is to compare its syntax and features with
those already known by the reader. Alas, Lisp has almost no syntax. Even if it did, that
approach would be a dismal failure. What makes Lisp interesting is its "feel of
development," which has no equivalent in Pascal or C++.
Do you remember first learning about object programming? How many times did you
have to hear about classes, instances, and inheritance before the concepts behind the
words finally took root? Do you truly understand them, or are there still things for
you to learn?
Lisp concepts are even more different than the concepts of object programming. A new
vocabulary filled with subtle connotations must be mastered. This can't be done by
reading a formal specification of the language. (Not to mention that the current
definition of Lisp is contained in a 1000 page book with a 10-point typeface.)
So my dilemma is this: do I take the safe route and compare Lisp syntax to that of
Pascal, or do I live dangerously, and try to present the "feel" of Lisp instead of Lisp
itself?
Since "feel" is subjective, I would risk accusations of language bigotry and
unprofessionalism. Perhaps even have my articles rejected. How embarrassing. Oh,
well. Life is short.
This first article is philosophical. It introduces the series, presents the concept of
"cost of innovation," explains (without yet proving) that Lisp provides a much lower
cost of innovation than MPW and MacApp, presents a basic Lisp tutorial, and concludes
with a guide and bibliography on how to learn Lisp.
The second article in the series will be more concrete. It will present additional Lisp
concepts, discuss the MCL development environment and tools (editor, debuggers,
etc.), and present CLOS, the Common Lisp Object System.
The third article will pull everything together. It will present the sample program
Game Of Life and discuss how developing Life in MCL was different than in Pascal and
MacApp. Both the development process and the quality of the final programs will be
considered, and source code for both versions will be included on the FrameWorks
Disk..
What is Lisp?
Lisp is the second oldest programming language still in use. It was developed by John
McCarthy at MIT in the 1950's. Lisp was originally designed for problems in calculus,
logic, game playing, and artificial intelligence.
Lisp rapidly evolved into incompatible dialects reflecting the needs of several
organizations; for example, MacLISP at MIT, ZetaLisp for Lisp machines, and
FranzLisp for Unix.
In 1984, a new dialect, Common Lisp, sought to create a portable, unified language on
which further extensions could be built. Common Lisp was well received and is now
working its way toward being an ANSI standard.
Many approaches to object programming have been implemented in Lisp. There has
been much cross-fertilization between Lisp and SmallTalk. A standard object
programming extension to Common Lisp, called Common Lisp Object System (CLOS),
which includes many ideas from these earlier approaches, has recently been accepted
as part of the forthcoming ANSI standard. CLOS is based on classes, multiple
inheritance, generic functions, and methods. It will eventually include a meta-object
protocol. CLOS's flexibility has to be seen to be believed.
Not everyone was happy with the standardization of Lisp. In particular, the fans of
Scheme, a Lisp dialect oriented toward teaching, objected to several decisions
incorporated in the standard. So it can be expected that other Lisp dialects will
continue to be popular.
A few years ago, Apple acquired Coral Software, whose flagship product was a version
of Lisp based on the 1984 Common Lisp standard. This was before CLOS, so Coral
developed their own object programming standard, Object Lisp, with a Macintosh Class
library built on top.
Apple has renamed the product Macintosh Common Lisp (MCL), replaced Object Lisp
with CLOS, brought it up to the latest almost-ANSI standard, improved the class
library, and released it in beta form, MCL 2.0b1. These articles are based on MCL
2.0b1.
Why Macintosh Common Lisp?
MCL offers a powerful environment for software development:
• A complete implementation of Lisp as defined in [Steele 1990], including
almost 1000 functions, macros and special forms.
• CLOS, a uniquely flexible object programming paradigm.
• A user interface class library.
• The full set of Lisp data types, including linked lists, arrays, structures,
objects and hash tables.
• FRED, a Lisp-sensitive text editor.
• Extensive debugging facilities, including single stepping, tracing,
interactive break points, and extensive object inspection.
Most important, however, is the Lisp approach to software development. I will now
explore two aspects of programming that show, by contrast, the Lisp approach. This is
a theoretical exploration; specific examples demonstrating these points will be
presented later.
The design approach
In university, computer science students are taught (or at least they used to be) that
the correct way to develop a software project is from the top down. In top-down
design, one starts with a description of the program and then breaks that into more
manageable subproblems until each subproblem piece can be easily coded. Traditional
languages encourage top-down development.
This approach assumes that while designing the top level we can ignore nasty realities
about the bottom levels; that is, that the chosen abstractions will protect the program
design from messy details.
Consider this painful counter-example from my past. In graduate school, I took a
software engineering course. Its final project required us to develop a certain
program. Computer facilities consisted of an IBM computer with punched card input
and PL/C, a teaching version of PL/1.
My project required a stack of punched cards almost a foot high. I started debugging at
10 a.m. the day before it was due. At 11 a.m., I had finished debugging individual
program components and was ready to put it all together. I figured on being done by
lunch. Ten frustrating hours later, I finally traced a program bug down to the fact that
PL/C did not handle subroutine parameter passing the same way as PL/1. My entire
design required this missing feature. I had to throw away my foot of cards, redesign,
retype, and re-debug the program; I was not happy.
Low level details can doom designs; so, in reality, programs must be developed both
from the top down and the bottom up. Top down to allow for consistency and clarity of
design, and bottom up to test that things work as expected in terms of both
functionality and performance.
Bottom-up development is a "bits and pieces" approach; you want to exercise a certain
program part quickly and in isolation from the rest of the program. Also, bottom up is
where a lot of innovation comes from. It is low level "tinkering" with the toolbox and
Macintosh hardware that's exciting, and that inspires me to explore new ways of doing
things.
Neither Pascal nor C++ makes "bits and pieces" development easy. Pascal and C++
programs are centered around a "main" block or function which imposes a
hierarchical ordering. It's difficult to test a program feature in isolation.
Assume you are in the late stages of developing a substantial C++ and MacApp program
and you have a brainstorm, possibly a better way to represent program data. How can
you experiment with this idea?
There are two choices. One is to incorporate your new idea in the current program.
Since the program is hierarchical, you must rip out the old code, develop a fairly
complete implementation of the new code, and hook it up. Even in a well designed
modular system this could take considerable effort, just to test an idea that might not
fly.
The second choice is to create a separate program to test out the idea. Setting up a new
program is costly under both MPW and Think. Additionally, the new data
representation may require part of the original program code to test. The code must be
incorporated piecemeal in the new program, a confusing and time consuming operation.
A Lisp program has no conventionally imposed hierarchical ordering. There is no
"main" function. A new data representation could easily be prototyped and tested in the
current program environment without bothering the working code. If successful, it
can be easily integrated. If not, it can be just as easily discarded. Thus there is no
penalty for exploring new ideas.
Turn-around time
Consider another contrived example. Assume you are developing a large program. Part
of this program displays a window with a square drawn in it. For some reason, you
want to know what it would look like with a circle instead. (I chose this example
because it explores the "cost" of a trivial experiment.)
On the IBM system described above, you would have to terminate the program, grab
your deck of cards, find the card that drew the square, repunch the card, schedule time
to use the graphics room, place the cards in the hopper, walk into the graphics room,
and wait for your program to be run. Average turn around would be about 24 hours.
On a Macintosh with MPW, you would terminate the program, return to MPW, find the
correct DrawContents method, change a line from FrameRect(r) to FrameOval(r),
rebuild the application, rerun it, and view the result. Turn around would be five
minutes or so.