Pearl Lisp
Volume Number: 6
Issue Number: 8
Column Tag: Programmer's Workshop
Objects in Pearl Lisp
By Stephan E. Miner, Menlo Park, CA
As a software engineer in the Information Sciences and Technology Center at SRI
International, Steve Miner works on applied research in artificial intelligence,
particularly in the areas of planning and decision aids. SRI’s hardware environment
consists mainly of Sun workstations and Symbolics Lisp Machines, but Steve gets to
use a Macintosh SE at home.
Introduction
Coral Software recently released Pearl Lisp, a subset of Common Lisp for about
$100. Pearl Lisp is based on Coral’s Allegro Common Lisp which has been discussed in
an earlier issue of MacTutor (see Paul Snively’s article in the March 1988 issue.)
Although Allegro received very good reviews, its list price of about $600 made it
impractical for most first-time Lisp programmers. Pearl Lisp offers an affordable
introduction to Lisp programming on the Macintosh.
Although a complete review of Pearl Lisp is not the purpose of this article, a
brief overview might be helpful. Some features of Common Lisp (such as packages,
structures, hash tables and multiple values) are not supported. However, most of the
list, control, and mathematical functions of Common Lisp are available. Lexical
scoping and closures are also implemented. The development environment is very
much like Allegro’s and includes an EMACS-like editor known as FRED (FRED
Resembles EMACS Deliberately.) The reference manual is well-written, with cross
references to Steele’s Common LISP: the Language and several popular textbooks. In
addition to the subset of Common Lisp functions, Pearl Lisp includes a native
object-oriented programming system, called Object Lisp, which is the topic of this
article.
As a demonstration of Object Lisp, the sample program simulates a solar system.
Objects are used to implement the planets as well as the Macintosh windows and menus.
Each planet orbits around a gravitational center at some radius with a certain period.
At any point in time, the planet has some X and Y coordinates relative to the center of
the solar system. The simulation is displayed in various windows with each window
offering the view from a particular planet. For example, a heliocentric view has the
Sun appearing fixed at the center of the window. In contrast, a geocentric view keeps
the Earth at the center of display with some of the other planets showing retrograde
motion. In both cases, the same planets are used and they maintain the same relative
motions. It is the window objects which implements the different points of view.
Object Lisp
Before describing the sample program in detail, a brief introduction to the basic
concepts of Object Lisp will be presented. As in other object-oriented programming
systems, an object encapsulates both data and functions. An object’s data is contained
in internal variables, known as instance variables. (Instance variables are often
called slots in other systems.) An object can also have specialized object functions
that allow access to the object’s internal variables as if the internal variables were
local to the function. (Object functions are often called methods in other
object-oriented systems.)
Inheritance is another key concept in most object-oriented systems. In Object
Lisp, objects are defined in terms of other objects. An object inherits the instance
variables and object functions of its parents. (Object Lisp supports multiple
inheritance, but the details are not discussed in this article.) An object can replace,
specialize, or combine the characteristics of its parents by shadowing the appropriate
object functions. A parent’s object function foo is also accessible by calling
usual-foo, even when the parent’s object function is shadowed.
The special form ask is used to access the environment of an object. For
example, suppose that OBJECT is bound to some object. The form (ASK OBJECT VAR)
will return the object’s internal value for VAR. The form (ASK OBJECT (FUNC
‘ARG)) will return the result of calling the object function FUNC with the argument
‘ARG.
Conceptually, global variables and functions belong to the root object nil. Thus,
(ASK NIL (GLOBAL-FUNC ‘ARG)) is equivalent to (GLOBAL-FUNC ‘ARG). This also
implies that global variables and functions are accessible by all objects through
inheritance from the root object. The root object also implements several useful
object functions. The have object function is used to create instance variables. For
example, (ASK OBJECT (HAVE ‘VAR ‘VAL)) will create an instance variable VAR with
an initial value VAL for the object OBJECT. The self object function simply returns
the current object.
Classes
Most object-oriented systems make a distinction between a class and an instance.
A class defines a type of object with a description of its internal data and the associated
procedures (or methods) for operating on that data. An instance, on the other hand, is
a specific object with internal values which hold its state. Object Lisp does not enforce
this strict distinction, but it does support a convention that implements classes as
types of objects.
Class objects can be created with the macro defobject which defines the name of
the class and the parents from which it inherits. Nil is used as the root object class.
An instance of a class is created by the function oneof which takes the parent
class as the first argument, followed by a list of keyword-value pairs for initializing
the instance. The initialization is actually executed by the exist object function
defined for the class. We will see how the exist object function works in our sample
program.
The Solar Program
The sample program can be divided into three major sections. The first part
implements the planet class. Window objects are implemented by the second part. The
final section provides menus for controlling the execution of the program.
The *planet* class is quite simple. It is defined using defobject and inherits
only from the root class object, nil.
The exist object function for *planet* needs more explanation. Exist will be
called automatically by the system when the oneof function is used to create a new
instance of*planet*. The init-list argument will be a list of the keyword-value pairs
given to oneof. The main purpose of the exist object function is to initialize the
instance variables. The getf function is used to access the value associated with the
indicated keyword. A default value can also be specified in case the keyword is not
found. For example, if the period of a planet is not specified in the init-list, the
default value of 25 is used.
In order to simplify the creation of new planets, the :center keyword is used to
specify the orbital center of the new planet. When calculating coordinates, however, it
is more natural to proceed from the center out to the satellites. The center is asked to
add the new planet to the its list of satellites using the add-satellite object function.
The update-system object function calculates the new X and Y coordinates for the
planet given the time and the coordinates of the planet’s orbital center. The planet then
recursively asks its satellites to run update- system. Thus, a single update-system
call to the sun of a solar system will be propagated through all the planets in the
system.
The other class that the program defines is the *solar- window*. This class is a
specialized version of the * window* class which is provided by Coral as an interface
to the Macintosh window system. This is a good example of how one can easily extend a
previously defined class. In this case, the new exist first calls usual-exist to handle
the normal initialization of the window. The init-list-default function returns an
init-list with the additional defaults. *Solar- window* also has a couple of its own
instance variables. The center instance variable determines the gravitational center
of the solar system (normally the *sun*.) The view instance variable controls the
viewpoint of the display. For example, a geocentric view is given by setting the view
to *earth*.
The center-origin object function resets the origin of the window to the center of
view. This simplifies the display of the solar system. The
window-zoom- event- handler and set- window-size object functions are also extended
to recenter the origin.
The inherited window-show object function displays the window on the screen.
This indirectly calls the window-draw-contents object function. The sample program
does not worry too much about animation flicker so it simply erases the entire window
and then redraws everything. The usual- window-draw-contents takes care of
redrawing the grow box.
The draw-system object function does most of the work. Its structure is similar
to update-system in that it draws one planet and then recursively draws the satellites
of that planet. In this case, however, the x-off and y-off arguments are offsets that
are added to the absolute coordinates for the planet to determine the planet’s window
coordinates. Each view assigns offsets so that the view planet remains centered in the
window. The rlet macro lets a Lisp program create Pascal record structures, such as
those used by the Macintosh ROM routines. Here, a rectangle is initialized based on the
size of the planet and its window coordinates. The Quickdraw routines, fill-oval and
frame-oval, are used to draw the planet. When rlet exits, the temporary record is
disposed automatically.
The erase-window object function also accesses a Pascal record structure. In
this case, it uses the rref macro to return the window’s portrect. The instance
variable, wptr, is defined by the * window* class and holds a Macintosh window
pointer. Rref allows the Lisp program to access the fields of the record using a
Pascal-style notation. The window is erased by calling the Quickdraw function
erase-rect.
The final section of the sample program involves the menu system. Once again,
the menus and menu items are predefined classes of objects. A menu-item-action
function is associated with each menu item. This function is called when the user
selects the item.