This tutorial is adapted from a 'XLISPOOP.DOC' document with the following copyright:
XLisp 2.0 Objects Primer by Tim I Mikkelsen - February 3, 1990
Copyright (c) 1990 by Tim I. Mikkelsen. All Rights Reserved. No part of this document may be copied, reproduced or translated for commercial use without prior written consent of the author. Permission is granted for non-commercial use as long as this notice is left intact.
One of the features in the design of XLISP is object-oriented programming. This primer is intended to serve as a very brief introduction to the object facilities of the XLISP 2.0 dialect of LISP. Note that the object features of XLISP are not based on other existing object definitions in other LISP dialects. If you find problems in the primer, I'd appreciate hearing.
Tim Mikkelsen, (tim@hpfcbig.SDE.HP.COM), 4316 Picadilly Drive, Fort Collins, Colorado 80526
There are many programming paradigms [models]. Some of the paradigms are procedural, functional, rule-based, declarative and object-oriented. A language can have aspects of one or many of these programming models.
Procedure-Oriented - The programming paradigm most people are familiar with is the procedural style. The primitives in procedural programming are: subroutines and data structures. Through these primitives, programmers have some limited abilities to share programs and program fragments. C and Pascal are examples of procedural languages. Some procedural languages [such as Modula and ADA] have extensions that provide for better sharing of code.
Object-Oriented - Object-oriented programming is based on the primitives of objects, classes and messages. Objects are defined in terms of classes. Actions occur by sending a message to an object. An object's definition can be inherited from more general classes. Objective-C and C++ both are object-oriented dialects of the C language. Many dialects of LISP have some object oriented extension [Flavors, Common LOOPS, CLOS and others]. There currently is standards work proceeding to add object-oriented programming to Common LISP.
So, the object-oriented programming model is based around the concepts of objects, classes and messages. An object is essentially a black box that contains internal state information. You send an object a message which causes the object to perform some operation. Objects are defined and described through classes.
One aspect of an object is that you do not have to know what is inside or how it works to be able to use it. From a programming point of view, this is very handy. You can develop a series of objects for someone to use. If you need to change what goes on inside, the users of the objects should be unaware.
Another aspect of objects is that of inheritance. You can build up new classes from existing classes by inheriting the existing class's functionality and then extending the new definition. For example, you can define a 'tool' class [with various attributes] and then go about creating object instances 'tool-1', 'tool-2', and so on. You can also create new sub-classes of the tool class like power-tool. This is also very handy because you don't have to re-implement something if you can build it up from existing code.
There are, as previously mentioned, many different languages with object-oriented extensions and facilities. The terminology, operations and styles of these are very different. Some of the main definitions for XLISP's object-oriented extensions are:
The 'object' data type is a built-in data type of XLISP. Members of the object data type are object instances and classes. | ||
An 'object instance' is a composite structure that contains internal state information, methods (the code which respond to messages), a pointer to the object instance's defining class and a pointer to the object's super-class. XLISP contains no built-in object instances. | ||
A 'class object' is, essentially, the template for defining the derived object instances. A class object, although used differently from a simple object instance, is structurally a member of the object data type. It is also contains the linking mechanism that allows you to build class hierarchies (sub-classes and super-classes). XLISP contains two built-in class objects: 'object' and 'class'. | ||
The 'message selector' is the symbol that is used to select a particular action (Method) from the object. | ||
The 'message' is the combination of the message selector and the data (if any) to be sent to the object. | ||
The 'method' is the actual code that gets executed when the object receives the Message. |
The mechanism for sending messages to XLISP objects is via the 'send' function. It takes an object, a message selector and various optional arguments [depending on the message selector].
The way that a user creates a new object is to send a ':new' message to a previously defined class. The result of this 'send' will return an object, so this is normally preceded by a 'setq'. The values shown in the examples that follow may not match what you see if you try this on your version of XLISP, this is not an error. The screens that are used in the various examples are similar to what you should see on your computer screen. The '>' is the normal XLISP prompt [the characters that follow the prompt is what you should type in to try these examples].
> (setq my-object (send object :new)) #<Object: #2e100>
The object created here is of limited value. Most often, you create a class object and then you create instances of that class. So in the following example, a class called 'my-class' is created that inherits its definition from the a built-in 'class' definition. Then two instances are created of the new class.
> (setq my-class (send class :new '())) #<Object: #27756> > (setq my-instance (send my-class :new)) #<Object: #27652> > (setq another-instance (send my-class :new)) #<Object: #275da>
Previously, a ':new' message was used to create an object. The message used to see what is in an object is the ':show' message.
> (send my-class :show) Object is #<Object: #27756>, Class is #<Object: #23fe2> MESSAGES = NIL IVARS = NIL CVARS = NIL CVALS = NIL SUPERCLASS = #<Object: #23fd8> IVARCNT = 0 IVARTOTAL = 0 #<Object: #27756>
From the display of the 'my-class' object you can see there are a variety of components. The components of a class are:
This pointer shows to what class the object [instance or class] belongs. For a class, this always points to the built-in object 'class'. This is also true of the 'class' object, its class pointer points to itself. | ||
This pointer shows what the next class up the class hierarchy is. If the user does not specify what class is the superclass, it will point to the built-in class 'object'. | ||
This component shows what messages are allowed for the
class, and the description of the method that will be used. If the
method is system-defined, it will show up in the form of:
#<Subr-: #18b98>Remember that the class hierarchy [through the Superclass Pointer] is searched if the requested message is not found in the class. |
||
This component lists what instance variables will be created when an object instance is created. If no instances of the class exist, there are no instance variables. If there are 5 instances of a class, there are 5 complete and different groups of the Instance Variables. | ||
and values |
The 'class variables' [cvar] component lists what class variables exist within the class. The Class Values [cval] component shows what the current values of the variables are. Class Variables are used to hold state information about a class. There will be one of each of the Class Variables, independent of the number of instances of the class created. |
The example previously shown does work, but the class and instances created don't really do anything of interest. The following example sets up a tool class and creates some tool instances.
> (setq my-tools (send class :new '(power moveable operation))) #<Object: #277a6> > (send my-tools :answer :isnew '(pow mov op) '((setq power pow) (setq moveable mov) (setq operation op))) #<Object: #277a6> > (setq drill (send my-tools :new 'AC t 'holes)) #<Object: #2ddbc> > (setq hand-saw (send my-tools :new 'none t 'cuts)) #<Object: #2dc40> > (setq table-saw (send my-tools :new 'AC nil 'cuts)) #<Object: #2db00>
So, a class of objects called 'my-tools' was created. Note that the class object 'my-tools' was created by sending the ':new' message to the built-in 'class' object. Within the 'my-tool' class, there are three instances called 'drill', 'hand-saw' and 'table-saw'. These were created by sending the ':new' message to the 'my-tools' class object. Notice that the parameters followed the message selector.
The following is a display of the contents of some of the previously created instances:
> (send drill :show) Object is #<Object: #2ddbc>, Class is #<Object: #277a6> POWER = AC MOVEABLE = T OPERATION = HOLES #<Object: #2ddbc> > (send hand-saw :show) Object is #<Object: #2dc40>, Class is #<Object: #277a6> POWER = NONE MOVEABLE = T OPERATION = CUTS #<Object: #2dc40>
From the display of these instances you can see there are some components and values. The components of an instance are:
This pointer shows to which class the current object instance belongs. It is through this link that the system finds the methods to execute for the received messages. | ||
and values |
The instance variables [ivar] component lists what variables exist within the instance. The instance values component holds what the current values of the variables are. Instance Variables are used to hold state information for each instance. There will be a group of Instance Variables for each instance. |
There have been a few of the messages and methods in XLISP shown to this point [':new' and ':show']. The following are the methods built into XLISP:
The ':answer' method allows you to define or change methods within a class. | ||
The ':class' method returns the class of an object. | ||
The ':isnew' method causes an instance to run its initialization code. When the ':isnew' method is run on a class, it resets the class state. This allows you to re-define instance variables, class variables, etc. | ||
The ':new' method allows you to create an instance when the ':new' message is sent to a user-defined class. The ':new' method allows you to create a new class (when the ':new' message is sent to the built-in 'class'). | ||
The ':show' method displays the instance or class. |
In addition to the 'send' function, there is another function called 'send-super'. The 'send-super' function causes the specified message to be performed by the superclass method. This is a mechanism to allow chaining of methods in a class hierarchy. This chaining behavior can be achieved by creating a method for a class with the ':answer' message. Within the body of the method, you include a 'send-super' form. This function is allowed only inside the execution of a method of an object.
The definition of the built-in class 'object' is:
> (send object :show) Object is #<Object: #23fd8>, Class is #<Object: #23fe2> MESSAGES = ((:SHOW . #<Subr-: #23db2>) (:CLASS . #<Subr-: #23dee>) (:ISNEW . #<Subr-: #23e2a>)) IVARS = NIL CVARS = NIL CVALS = NIL SUPERCLASS = NIL IVARCNT = 0 IVARTOTAL = 0 #<Object: #23fd8>
Note that 'object' is a class, as opposed to an 'instance-style' object. 'object' has no superclass, it is the top or root of the class hierarchy. 'object's class is 'class'.
> (send class :show) Object is #<Object: #23fe2>, Class is #<Object: #23fe2> MESSAGES = ((:ANSWER . #<Subr-: #23e48>) (:ISNEW . #<Subr-: #23e84>) (:NEW . #<Subr-: #23ea2>)) IVARS = (MESSAGES IVARS CVARS CVALS SUPERCLASS IVARCNT IVARTOTAL) CVARS = NIL CVALS = NIL SUPERCLASS = #<Object: #23fd8> IVARCNT = 7 IVARTOTAL = 7 #<Object: #23fe2>
'class' has a superclass of 'object'. It's class is itself, 'class'.