home *** CD-ROM | disk | FTP | other *** search
- In article <1511@dinl.mmc.UUCP> noren@dinl.UUCP (Charles Noren) writes:
- >It's been a while since I've been on the net. I can no longer
- >access comp.lang.lisp.x from our site, I suppose it went away?
-
- it's still there...
-
- >I've just starting playing with XLISP v2.0, particularly the
- >object-oriented features of it. I've created new classes with
- >instance and class variables, and I've used the :new selector
- >to do so and it works just fine. However, I see the :isnew
- >selector in the documentation and I was wondering how that works
- >compared to :new.
-
- When I first looked at XLISP, I too found the documentation on the
- object system to be a little terse. Everything becomes much clearer
- once you see some examples.
-
- I recently wrote up some documentation on XLISP's object system for
- use with WINTERP (an XLISP-based rapid prototyping environment for
- applications based on the OSF Motif widgets). The following excerpt
- from winterp/doc/winterp.doc may help (get winterp via anonymous ftp
- from expo.lcs.mit.edu:oldcontrib/winterp.tar.Z). In particular, your
- question about :ISNEW is answered in the "object initialization"
- section.
-
- --------------------
-
- * Introduction to XLISP objects and Widgets.
-
- WINTERP uses XLISP's object system as its interface to the class hierarchy
- of widgets provided by Motif. Specifically, each Motif widget class is
- represented by one or more object classes in WINTERP. In order to best
- understand the capabilities of WINTERP's Motif interface, a brief review of
- the XLISP object system is in order. You may also want to consult the XLISP
- documentation ./winterp/doc/xLisp.doc for a more precise definition of the
- object system.
-
- XLISP Classes describe the type of a particular object by declaring a set
- of variables held in each object. These "instance variables" may only be
- accessed by "methods" that respond to "messages" sent to the object.
- Methods are defined for particular classes, and functionality of other
- classes may be incorporated into new classes via "inheritance". From
- XLISP, Motif widget classes look just like normal XLISP objects -- that
- means that you can easily extend the functionality of Motif widgets by
- adding your own methods to a particular widget class. You may also use
- inheritance to attach your own data structures to widgets. The result is
- that WINTERP provides a very clean way to interactively rapid-prototype an
- application, while also providing mechanisms for code structuring and reuse.
- The latter is necessary in evolving from prototype to a structured,
- maintainable, and customizable deliverable.
-
-
- ** Creating new objects.
-
- Create a new instance of a class by sending the message :NEW to
- <a_class_instance>:
-
- (SEND <a_class_instance> :NEW <parameters>)
-
- <a_class_instance> is in fact an instance of class CLASS. Class CLASS allows
- you to define new class instances by specifying the instance variables and
- parent class of a particular class.
-
-
- ** Declaring a class.
-
- To declare a "base class" object, that is, an object with no parent object,
- just send message :NEW to the object <CLASS>
-
- (SEND CLASS :NEW '(<ivar0> ... <ivarN>)
- ['(<cvar0> ... <cvarM>)])
-
- '(<ivar0> ... (ivarN>) are a list of symbols. Each <ivar-i> names an
- instance variable of the class. '(<cvar0> ... <cvarM>)]) are an optional
- list of variables that are shared among all instances of that particular
- class.
-
-
- ** Defining methods.
-
- When a "message" is sent to an object, XLISP searches for a "method" to
- answer the message. A method is a piece of Lisp code that is executed when
- a particular message is sent to an object. Within the code of a method, all
- object instance and class variables are accessible. Furthermore, the symbol
- 'self' is bound to the object the message was sent to.
-
- Methods are defined by sending the message :ANSWER to <a_class_instance>:
-
- (SEND a_class_instance :ANSWER <:msg> <parameters> <code>)
-
- where <:msg> is a keyword symbol (a symbol with a ':' prefix) representing
- the message; <parameters> are the arguments given along with the message.
- See the documentation on "lambda lists" in /winterp/doc/xLisp.doc p.12 for
- details. <code> is a list of s-expressions which get evaluated in response
- to a message. The lexical environment that existed for the call to :ANSWER
- will be used for value and functional bindings during method evaluation.
-
- If you need to remember what the syntax is, consider the memory-helper
- "this class :ANSWERs to :MESSAGE..." == (send <cls> :ANSWER :MESSAGE ...)
-
-
- ** Inheritance
-
- So far, the object system we just described supports *encapsulation*.
- Encapsulation is good programming practice because it helps localize and
- detangle complexity. Unfortunately, encapsulation runs counter to
- flexibility because making flexible use of an object may require that
- certain groups of instance variables be accessed by different layers of new
- functionality. Most often, one wants to *reuse* aspects of a particular
- class in creating code that specializes and alters the functionality of
- that class. A compromise between encapsulation and flexibility is found by
- using *inheritance* in an object system. Inheritance is used to allow a
- *subclass* to specialize the functionality of it's *parent class* (aka,
- the *superclass*):
-
- (send Class :NEW '(<ivar0> ... <ivarN>)
- '(<cvar0> ... <cvarM>)
- <superclass>)
-
- (<ivar0> ... <ivarN>) is a list of new instance variables in the subclass;
- (<cvar0> ... <cvarN>) is a list of new class variables in the subclass;
- <superclass> is a class instance representing the parent from which
- the new subclass inherits variables and methods.
-
- "Inheritance" is occurring because all the instance variables of all the
- parent classes of the new subclass become the variables of each subclass
- instance. Furthermore, all methods defined on a parent class may also be
- used on a subclass instance. Note that while a subclass' methods can access
- the variables defined on the parent classes, the reverse isn't true.
-
-
- ** Object initialization.
-
- As mentioned earlier, new object instances are created by sending the
- message :NEW to a class object. Sending the message :NEW to a class
- automatically sends message :ISNEW to the newly created instance. By
- default :ISNEW on an instance is a no-op method defined on class 'Object',
- which is the implicit [(grand)*]parent of all instances. If you want to
- initialize the instance/class variables of a particular class, you must
- define an :ISNEW method on the class. Any parameters originally sent to
- the :NEW method will be passed on to the :ISNEW method and may be used to
- initialize an object to outside-world parameters.
-
-
- ** Example of using OOP features of XLISP with Motif widgets:
-
- As currently implemented, the Motif class xmListWidgetClass makes it a bit
- baroque to create browsers (hopefully this will change in Motif 1.1). The
- problem is that a "browser" is a kind of application that lends itself to
- object oriented techniques that are not always straightforward to support
- in C. One often has a collection of 'things' that one wants to display in a
- 'list' and perform actions on the 'thing' corresponding to the visual
- selection of an element in the displayed list. xmListWidgetClass will
- display an arrray of XmStrings in a list. When one or more elements in the
- list are selected, XmStrings corresponding to the selected elements are
- returned. Since the XmStrings you put into the list widget are not the
- XmStrings you get out, you must call XmStringCompare on each element of the
- collection of 'things' to find out which ones are selected. Presumably,
- you'll want to do more than just get back an XmString... normally one will
- want to access data structures associated with the XmString so as to perform
- an action dependent on those structures. This could be done with a lookup
- table, but there's also a better way...
-
- WINTERP allows us to subclass the Motif list widget so as to make it have
- the kind of functionality we want. This subclass will contain an additional
- instance variable 'items' which is an array of arbitrary XLISP objects to
- be displayed in a textual browser made from the list widget. These objects
- can have completely different internal representations -- the only
- requirement is that they follow the protocol of being able to respond to
- the messages :DISPLAY_STRING and :DEFAULT_ACTION. :DISPLAY_STRING returns a
- string representation of the object to be displayed in the browser.
- :DEFAULT_ACTION is the action to be performed when a list item is browsed
- (by double clicking on the item).
-
- The following creates the subclass <List_Browser> from superclass
- <XM_LIST_WIDGET_CLASS>:
-
- (setq List_Browser
- (send Class :NEW ;create a class inst
- '(items) ;new instance vars
- '() ;no class vars
- XM_LIST_WIDGET_CLASS)) ;superclass
-
- So now all instances of <List_Browser> will contain an instance variable
- <items> and will respond to all the messages understood by the
- XM_LIST_WIDGET_CLASS. We want our list browser to behave as described
- above, so we define an :ISNEW method to initialize instance variable
- <items> to the list of arbitrary objects to be displayed. <items> gets
- initialized to an array of objects, the list widget is created, and a
- XmNdefaultActionCallback is setup so that a double click will send the
- message :DEFAULT_ACTION to the browsed item:
-
- ;; (send List_Browser :new <items_list> <args-for-the-list-widget>)
- ;; <items_list> is a list of BROWSER_OBJECTs as described above.
- ;; <args-for-the-list-widget> -- these are the arguments that
- ;; will be passed on to the list widget
- ;;
- (send List_Browser :answer :isnew '(items_list &rest args)
- '(
- (let* (
- (items_end_idx (length items_list))
- (display_items (make-array items_end_idx)))
-
- ;; initialize the 'items' instance variable so that it
- ;; holds all the BROWSER_OBJECTs passed in <items_list>
- (setq items (make-array items_end_idx)) ;create the array
- (do ( ;copy elts from list to array
- (i 0 (1+ i))
- (elts items_list (cdr elts)))
- ;; loop till no more elts
- ((null elts))
- ;; loop body
- (setf (aref items i) (car elts))
- (setf (aref display_items i)
- (send (car elts) :display_string))
- )
-
- ;; initialize the widget, passing in the browser items.
- (apply 'send-super `(:isnew
- ,@args
- :xmn_selection_policy :browse_select
- :xmn_items ,display_items
- :xmn_item_count ,items_end_idx
- ))
- )
-
- ;; set up a callback on the list widget initialized above such
- ;; that a double click on the browser-item will send the
- ;; message :default_action to the BROWSER_OBJECT.
- (send-super :add_callback :xmn_default_action_callback
- '(callback_item_position)
- '((send (aref items (1- callback_item_position))
- :default_action))
- )
- )
- )
-
-
- In the above code, SEND-SUPER works just like send, except that it doesn't
- require you to give it the object to send the message to. Instead, it
- implicitly assumes that it will be called from within a method, and will
- automatically send the message to a superclass of the object's class. The
- (apply 'send-super ...) form is actually calling the :ISNEW (instance
- initializer) method on XM_LIST_WIDGET_CLASS, which actually creates the
- widget via XmCreateList() or XmCreateScrolledList(). The APPLY '`'
- (BACKQUOTE) and '&rest args' (LAMBDA LIST) features of Lisp allow us to
- splice in the argument list passed to the instance of List_Browser into the
- function that actually creates the widget. Finally, method :add_callback is
- the WINTERP equivalent of XtAddCallback(). See the documentation on methods
- on WIDGET_CLASS for more details.
-
- The Motif List widget also defines a number of "methods" implemented as C
- routines such as XmListAddItem(), XmListAddItemUnselected(),
- XmListDeleteItem(), and XmListDeletePos(). In WINTERP, we define these as
- methods :ADD_ITEM, :ADD_ITEM_UNSELECTED, :DELETE_ITEM, and :DELETE_POS
- respectively. Since these methods modify the collection of objects
- represented by the list widget, we must update the internal array of
- objects <items> to correspond with the items displayed. We do this by
- intercepting those messages to the superclass of class <List_Browser> and
- handle them in <List_Browser> so as to update the appropriate data:
-
- (send List_Browser :answer :ADD_ITEM '(item position)
- '(
- (setq items (array-insert-pos items (1- position) item))
- (send-super :add_item
- (send item :display_string)
- position)
- )
- )
-
- (send List_Browser :answer :ADD_ITEM_UNSELECTED '(item position)
- '(
- (setq items (array-insert-pos items (1- position) item))
- (send-super :add_item_unselected
- (send item :display_string)
- position)
- )
- )
-
- (send List_Browser :answer :DELETE_ITEM '(item)
- '(
- ;; this is too lame to implement... requires that we compare
- ;; item with the result of :display_string done on every elt
- ;; of ivar 'items'
- (error "Message :DELETE_ITEM not supported in List_Browser")
- )
- )
-
- (send List_Browser :answer :DELETE_POS '(position)
- '(
- (setq items (array-delete-pos items (1- position)))
- (send-super :delete_pos position)
- )
- )
-
- To see how this subclassed list browser is used, and also to see how one
- might write a sample application in WINTERP using the object oriented
- features of XLISP, see ./winterp/examples/grep-br.lsp. That file
- implements a simple search browser based on the UN*X command 'grep'. See
- also ./winterp/examples/mail-br.lsp to see how you can build a simple
- mh-based mail browser. Finally, as another example of subclassing Motif
- widgets, see ./winterp/examples/radiobox2.lsp.
- -------------------------------------------------------------------------------
- Niels Mayer -- hplabs!mayer -- mayer@hplabs.hp.com
- Human-Computer Interaction Department
- Hewlett-Packard Laboratories
- Palo Alto, CA.
- *
-
-
-