; Demonstration of classes in XLISP by implementing various Smalltalk ; Collection classes. ; Author: Tom Almy ; Date: September 1996 ; NOTE -- you should probably check out EXAMPLE.LSP, TURTLES.LSP, and ; BLOCKS.LSP first as they are somewhat simpler. #-:classes (load "classes") ; We'll use these nice macros ; We will put everyting in a package to keep it out of the user name space #+:packages (unless (find-package "CLASSES") (make-package "CLASSES" :use '("XLISP"))) (in-package "CLASSES") ; List the symbols available on the outside -- in this case the class names. ; The message selectors are all in the keyword package so they don't need ; to be exported. (export '(Collection Set Bag Dictionary SequenceableCollection Array OrderedCollection SortedCollection Interval)) ; Our basic Collection class is "abstract" -- it's just defined to ; subclass into useful types of collections. We'll define a single instance ; variable: "data" contains the collection's data, the format to be ; defined by the subclass. Various subclasses will define any additional ; instance variables. ; The actual collections used in applications will be created from subclasses ; of Collection. This demo will implement: ; Bag -- an unordered collection of objects ; Set -- like a bag, but no duplicate elements ; Dictionary -- access elements using symbolic keys ; SequenceableCollection -- Abstract class which is subclassed into: ; Array -- elements have a sequence. Collection has a fixed size ; OrderedCollection -- same as Array but no fixed size, can add/delete from ; either end. ; SortedCollection -- An Ordered collection with a colating sequence ; Interval -- contains a constant sequence ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE COLLECTION CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass Collection (data)) ; The defclass macro defines a new class, which is bound to the symbol ; "Collection". The macro also defines several default methods for instance ; variable access (:data in this case), and instance initialization (:isnew). ; Unlike Smalltalk, XLISP has no class methods. In Smalltalk you create an ; instance of a class by sending a message to the class. In XLISP, the classes ; are members of class Class, and you create an instance by sending the class ; a message with the selector :new. i.e. (send MyClass :new ) where xxx ; are 0 or more arbitrary expressions. This executes the :new method in class ; Class which will create an object which is a member of the desired class ; (call it newobj) and then does a (send newobj :isnew ). The :isnew ; method gets the expressions, which it can then use to customize the ; initiation. So, basically, the :isnew method in a class takes the place of ; the class methods of Smalltalk. Not as functional, but it usually does all ; that is needed. The class Array demonstrates how two instance creation ; methods can be supported via the use of keyword arguments. ;;;;;;;;; ; The following group of methods are "private" methods in that they are not ; intended for use in applications, but just to aid in implementation. ; :notImplemented provides a nice error message for messages that aren't ; handled by our class. It's not really necessary to define these! (defmethod Collection :notImplemented (msg) (error "~s not handled in class ~a" msg (send (send self :class) :pname))) ; Here's the difference: ; >(send x :foo) :notImplemented USED ; error: no method for this message - :foo ; >(send x :foo) :notImplemented NOT USED ; error: :foo not handled in class Bag ; :map is a mapcar like mapping function for the collection. ; This version only works when data instance variable is sequence of ; collection elements. We will have to override the method for subclasses ; that maintain their data differently. (defmethod Collection :map (fcn) (map 'cons fcn data)) ; :addAll will add the elements in the argument collection to this ; collection. We'll extend this definition so it works with sequences as ; well. It won't work with Arrays (which are a fixed size), Intervals ; (which are not alterable), or Dictionaries (which require keys). (defmethod Collection :addAll (arg) (if (or (listp arg) (arrayp arg) (stringp arg)) ; Use map when argument is a sequence (map nil (lambda (x) (send self :add x)) arg) ; Otherwise, send :map to the argument collection (send arg :map (lambda (x) (send self :add x)))) self) ; Override default "isnew" to disallow creating abstract collections. ; There is no reason for any program to create an instance of Collection. (defmethod Collection :isnew (&rest dummy) (error "Don't create collections of class \"Collection\"")) ;;;;;;;;; ; Now we will define some "public" methods for Collection. Most will be ; overriden in a subclass. The rest we will provide with a common default ; functionality. ; :prin1 determines how an object is printed. The default is to print ; the objects class and unique ID. We want to do better than that if the ; collection is small enough to easily display, say 5 or fewer elements (defmethod Collection :prin1 (&optional (stream *standard-output*)) (let ((contents (send self :asList)) ; get collection as a list (cls(send(send self :class):pname))) ; and get our class' name (cond ((null contents) (format stream "#" cls)) ((< (length contents) 6) (format stream "#<~a:~{ ~s~}>" cls contents)) (t (format stream "#<~a:~5{ ~s~} ...>" cls contents))))) ; :storeon is used to create an expression which, when executed, will create a ; copy of the object. The Default method, part of class Object, won't work ; for classes that override :isnew, and all Collection classes do. (defmethod Collection :storeon () (list 'send (list 'send (intern (send (send self :class) :pname)) :new) :addAll (list 'quote (send self :asList)))) ; :at will fetch an element from an "sequenceable collection" ; Not all collections have the concept of sequencing. (defmethod Collection :at (arg) (send self :notImplemented :at)) ; :atput will store an element into a "sequenceable collection". (defmethod Collection :atPut (arg1 arg2) (send self :notImplemented :atPut)) ; :first will fetch the first element of the collection, where appropriate. ; :last does the same thing but for the last element. (defmethod Collection :first () (send self :notImplemented :first)) (defmethod Collection :last () (send self :notImplemented :last)) ; :add will store (one or more copies of) an element into a collection ; :addFirst will add to the start of a collection. These two are not ; implemented for all classes. (defmethod Collection :add (arg &optional value) (send self :notImplemented :add)) (defmethod Collection :addFirst (arg) (send self :notImplemented :addFirst)) (defmethod Collection :addLast (arg) (send self :notImplemented :addLast)) ; Delete the specified, first, or last element (defmethod Collection :remove (arg) (send self :notImplemented :remove)) (defmethod Collection :removeFirst () (send self :notImplemented :removeFirst)) (defmethod Collection :removeLast () (send self :notImplemented :removeLast)) ; :size -- Get the size of the the Collection. This will work for ; most subclasses. (defmethod Collection :size () (length data)) ; :empty -- Returns T if collection has no elements (defmethod Collection :empty () (zerop (send self :size))) ; :includes tells us if a object is a member of the collection ; This version only works when data instance variable is sequence of ; collection elements (defmethod Collection :includes (arg) (if (position arg data) t nil)) ; :species returns the class similar to the current class to create new ; objects (defmethod Collection :species () (send self :class)) ; :do is like :map but returns nothing ; :collect is like :map, but returns a new collection. ; :select returns a collection of elements for which the predicate function ; returns non-NIL. ; These are generic enough to work for any of the Collection subclasses ; except Array, which requires an argument to :new, ; however in many cases they could be overridden for speed. ; Smalltalk defines these and a number of similar functions. (defmethod Collection :do (fcn) (send self :map fcn) nil) (defmethod Collection :collect (fcn) (send (send (send self :species) :new) :addAll (send self :map fcn))) (defmethod Collection :select (fcn) (let ((result (mapcan (lambda (x) (when (funcall fcn x) (list x))) (send self :asList)))) (send (send (send self :species) :new) :addAll result))) ; Our final assortment of Collection methods create copies of the object in ; one of several Collection subclasses or as an LISP list. ; :asList will return the collection as a LISP linked list. (defmethod Collection :asList () (send self :map #'identity)) ; :asBag will return the collection as a Bag (defmethod Collection :asBag () (let ((result (send Bag :new))) (send result :addAll self) result)) ; :asSet will return the collection as a Set (defmethod Collection :asSet () (let ((result (send Set :new))) (send result :addAll self) result)) ; :asArray will return the collection as an Array (defmethod Collection :asArray () (send Array :new :initial (send self :asList))) ; :asOrderedCollection will return the collection as an OrderedCollection (defmethod Collection :asOrderedCollection () (let ((result (send OrderedCollection :new))) (send result :addAll self) result)) ; :asSortedCollection will return the collection as an OrderedCollection (defmethod Collection :asSortedCollection (&optional (fcn '<)) (let ((result (send SortedCollection :new fcn))) (send result :addAll self) result)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE SET CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Our first collection will be "Set". Initialization doesn't have to do ; anything since instance variables are initialized to NIL. ; We will use "eql" as the equality test ; (defclass Set () nil Collection) (defmethod Set :isnew ()) ; We will need :add. But we will ignore the count. (defmethod Set :add (arg &optional (count 1)) (setq data (adjoin arg data)) ; Methods typically return (or "answer" in Smalltalk) ; the object, which is bound to "self", if there is ; nothing more appropriate. self) ; We also need to define :remove (defmethod Set :remove (arg) (let ((pos (position arg data))) ; Find (first) instance (when pos ; Delete found element (if (zerop pos) (setq data (cdr data)) (setf (cdr (nthcdr (1- pos) data)) (nthcdr (1+ pos) data)))) self)) ; All the other methods inherited from Collection will work fine ; At last we can test out some collections! ; > (setq x (send Set :new)) Create a new set ; # ; Note that if your system says "#" that means you have ; *readtable-case* set to :upcase. It's nothing to be concerned about, but ; if you want the output to match, start over with *readtable-case* set to ; :invert. ; > (send x :add 3) Add the element "3" ; # ; > (send x :add 1) Add the element "1" ; # ; > (send x :add 3) Add another 3 -- it's ignored! ; # ; > (send x :addAll '(1 2 3 4 5)) Add five elements ; # ; We see the order has changed! This doesn't matter because these collections ; are defined to have no order. ; > (send x :remove '3) Remove element "3" ; # ; > (send x :select #'evenp) Create a set with even elements of x ; # ; > (send x :collect #'1+) Create a set with incremented ; elements of x ; # ; > (let ((cnt 0)) (send x :do (lambda (x) (incf cnt x))) cnt) ; 12 Summing all the elements in the set ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE BAG CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Our next Collection class will be "Bag" which is an unordered collection ; of objects that we will implement with a hash table. The table value will ; be the number of occurances of the object in the collection. ; It's difficult to calculate the number of elements in a Bag, so we will ; maintain a running total in a new instance variable, size. The defclass ; function will create a :size method for us! ; ; After we've defined this class, we can finally start testing things out. (defclass Bag (size) nil Collection) ; Because the data in a Bag will be a hash table instead of a list, we ; need to have "isnew" allocate a hash table. ; The entry equality test will be "eql" (defmethod Bag :isnew nil (setf (send self :data) (make-hash-table) (send self :size) 0)) ; We could have done this with "(setf data (make-hash-table) size 0)" ; but this technique is more rigorous. ; The method :add will insert one or more copies of an object in the collection ; We need to adjust the size instance variable when we add objects (defmethod Bag :add (arg &optional (count 1)) (setf (gethash arg data) (+ (gethash arg data 0) count) size (+ size count)) self ; Most methods return Self if there isn't anything else ) ; that is reasonable ; The method :remove will delete an object from the collection ; We need to adjust the size instance variable when we delete objects (defmethod Bag :remove (arg) (let ((cnt (gethash arg data))) (when cnt ; element found (setq size (1- size)) (if (= cnt 1) (remhash arg data) ; delete if count would be 0 (setf (gethash arg data) (1- cnt)))) self )) ; We have to override the definition of :includes since data is stored ; differently in a bag than as a linked list. (defmethod Bag :includes (arg) (if (gethash arg data) t nil)) ; We have to override the definition of :map since data is stored ; differently in a bag than as a linked list. ; Even though :collect is similar, we don't need to redefine it since ; Collection :collect uses :map to do its work. (defmethod Bag :map (fcn) (if data ; If in the rare case data isn't set up, we abort (let (result) (maphash (lambda (arg count) (dotimes (i count) (push (funcall fcn arg) result))) data) (nreverse result)) nil)) ; Now for some Bag examples: ; > (setq y (send Bag :new)) Create a new bag, y ; # ; > (send y :add 3) As with set, add 3, 1, 3 ; # ; > (send y :add 1) ; # ; > (send y :add 3) ; # Now there can be multiple copies! ; > (send y :addAll x) Add all the elements of Set x ; # Elipsis means too many to display ; > (send y :asList) Use :asList to see entire contents ; (5 4 3 3 2 1 1) ; > (send y :remove 4) ; # Remove still works ; > (send y :select #'oddp) Try :select :collect and :do ; # ; > (send (send y :collect #'1+) :asList) ; (6 4 4 3 2 2) ; > (let ((cnt 0)) (send y :do (lambda (x) (incf cnt x))) cnt) ; 15 ; > (send y :asSet) Converting a Bag to a Set ; # will delete duplicates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE DICTIONARY CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The Dictionary class will be implemented using a hash table like a Bag. ; The hash table will use #'equal for comparisons, allowing string keys ; (defclass Dictionary () nil Collection) (defmethod Dictionary :isnew nil (setf (send self :data) (make-hash-table :test #'equal))) ; Getting the size of a Dictionary is slightly different than the default (defmethod Dictionary :size () (hash-table-count data)) ; We need to define the :at and :atPut methods. :at will be extended ; to allow a keyword argument "ifAbsent" to supply the return value. ; It's a closure, just like in Smalltalk (setq gened (gensym)) ; We need a unique symbol (defmethod Dictionary :at (key &key ifAbsent) (let ((value (gethash key data '#.gened))) (if (eq value '#.gened) (if ifAbsent (funcall ifAbsent) nil) value))) (defmethod Dictionary :atPut (key value) (setf (gethash key data) value) self) ; :addAll needs to be redefined, and requires a list of key-value pairs. ; This method makes :storeon much simpler. (defmethod Dictionary :addAll (arg) (if (or (listp arg) (arrayp arg) (stringp arg)) ; Use map when argument is a sequence (map nil (lambda (x) (send self :atPut (first x) (second x))) arg) ; Otherwise, send :map to the argument collection (send arg :map (lambda (x) (send self :atPut (first x) (second x))))) self) ; :remove won't work for a Dictionary, since we want to remove key/value ; associations. Thus we have :removeKey, with an optional ifAbsent. (defmethod Dictionary :removeKey (key &key ifAbsent) (if (eq (gethash key data '#.gened) '#.gened) (progn (remhash key data) (setq count (1- count))) (when ifAbsent (funcall ifabsent))) self) (unintern gened) ; We don't need this symbol anymore ; :keys returns a set of the keys (defmethod Dictionary :keys () (let (list) (maphash (lambda (key value) (setq list (cons key list))) data) (send (send Set :new) :addAll list))) ; :values returns a bag of the values (defmethod Dictionary :values () (let (list) (maphash (lambda (key value) (setq list (cons value list))) data) (send (send Bag :new) :addAll list))) ; :map is defined to work over the values (defmethod Dictionary :map (fcn) (let (list) (maphash (lambda (key value) (setq list (cons (funcall fcn value) list))) data) list)) ; We have to override the definition of :includes since data is stored ; differently in a Dictionary than as a linked list. (defmethod Dictionary :includes (arg) (if (position arg (send self :asList)) t nil)) ; :collect, :select aren't appropriate (defmethod Dictionary :collect (arg) (send self :notImplemented :collect)) (defmethod Dictionary :select (arg) (send self :notImplemented :select)) ; :prin1 needs to be overridden to show both keys and data (defmethod Dictionary :prin1 (&optional (stream *standard-output*)) (let (contents ; get collection as a list ; and get our class' name ; (it might not be "Dictionary") (cls (send (send self :class) :pname))) (maphash (lambda (x y) (setq contents (cons (list x y) contents))) data) (cond ((null contents) (format stream "#" cls)) ((< (length contents) 6) (format stream "#<~a:~{ ~s~}>" cls contents)) (t (format stream "#<~a:~5{ ~s~} ...>" cls contents))))) ; A different :storeon is needed as well (defmethod Dictionary :storeon () (let (contents) ; get collection as a list (maphash (lambda (x y) (setq contents (cons (list x y) contents))) data) (list 'send (list 'send 'Dictionary :new) :addAll (list 'quote contents)))) ; Class Dictionary examples ; > (setq z (send Dictionary :new)) Create a new dictionary ; # ; > (send z :addAll '((a 1) (b 2) (c 3) (d 4))) Quickly add 4 entries ; # ; > (send z :at 'b) Given a key, returns value ; 2 ; > (send z :at 'e :ifAbsent (lambda () "Key Not Found")) Check ":ifAbsent" ; "Key Not Found" ; > (send z :atPut 'b 7) :atPut will change value ; # ; > (send z :atPut 'e 100) :atPut will create new entries ; # ; > (send z :asBag) Converting to Bag just gives ; # values ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE SEQUENCEABLECOLLECTION CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The class SequenceableCollection is, like Collection, an abstract class. ; This is a good thing since who would want to type "SequenceableCollection" ; very often? ; (defclass SequenceableCollection () nil Collection) ; Some methods can be defined that will work for all subclasses of ; SequenceableCollection. The minimum index value is 0. (defmethod SequenceableCollection :at (arg) (elt data arg)) (defmethod SequenceableCollection :atPut (arg value) (setf (elt data arg) value) self) (defmethod SequenceableCollection :first () (self :at 0)) (defmethod SequenceableCollection :last () (self :at (1- (send self :size)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE ARRAY CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The Array class -- implemented using an array. ; Because it has a fixed size, we have to allocate space for it when ; we create it. We will allow for initialization, since :addAll won't work. ; Either : (send Array :new :size 10) for example, to create an array of 10 ; entries or (send Array :new :initial (1 2 3 4 5)) for an initialized array. (defclass Array () nil SequenceableCollection) (defmethod Array :isnew (&key size initial) ; Size must be specified when creating array (if size (setf (send self :data) (make-array size)) (setf (send self :data) (make-array (length initial) :initial-contents initial)))) ; We have to override :collect because (send Array :new) won't work. ; But we can optimize while we are at it. (defmethod Array :collect (fcn) (let ((result (send Array :new :size (send self :size)))) (map-into (send result :data) fcn data) result)) ; We also have to override :select, for the same reason (defmethod Array :select (fcn) (let ((result (mapcan (lambda (x) (when (funcall fcn x) (list x))) (coerce (send self :data) 'list)))) (send (send self :class) :new :initial result))) ; Finally, :storeon needs to be changed since :addAll doesn't work for ; arrays. (defmethod Array :storeon () (list 'send 'Array :new :initial (list 'quote (send self :asList)))) ; Test of the Array class: ; > (setq a (send x :asArray)) Make Array from Set x ; # ; > (send a :atPut 1 10) Change an element ; # ; > (send a :select #'evenp) Get array of even elements ; # ; > (send a :collect #'1+) Make array with values 1 larger ; # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE ORDEREDCOLLECTION CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The OrderedCollection class uses linked lists and doesn't have the ; allocation problems of Array. ; Adding or deleteing from the start alters the index numbers, ; so we need a new instance variable to hold the offset. (defclass OrderedCollection (offset) nil SequenceableCollection) (defmethod OrderedCollection :isnew (&optional (offset 0)) ; Optional argument sets offset of first element. ; This is a "private" feature to aid storeon. (setf (send self :offset) offset)) ; :at, :atPut, :first, and :last need revision (defmethod OrderedCollection :at (arg) (elt data (+ arg offset))) (defmethod OrderedCollection :atPut (arg value) (setf (elt data (+ arg offset)) value) self) (defmethod OrderedCollection :first () (car data)) (defmethod OrderedCollection :last () (car (last data))) ; We need to implement add and remove for both ends ; :add will be equivalent to :addLast (defmethod OrderedCollection :add (arg) (setq data (nconc data (list arg))) self) (defmethod OrderedCollection :addlast (arg) (send self :add arg)) (defmethod OrderedCollection :addFirst (arg) (setq offset (1+ offset)) (setq data (cons arg data)) self) (defmethod OrderedCollection :removeFirst () (unless (zerop (length data)) (setq offset (1- offset)) (prog1 (car data) (setq data (cdr data))))) (defmethod OrderedCollection :removeLast () (prog1 (car (last data)) (setq data (nbutlast data)))) ; Finally, storeon is modified so that offset will be set (defmethod OrderedCollection :storeon () (list 'send (if (zerop offset) (list 'send (intern (send (send self :class) :pname)) :new) (list 'send (intern (send (send self :class) :pname)) :new offset)) :addAll (list 'quote (send self :asList)))) ; Example of use of OrderedCollection: ; > (setq c (send a :asOrderedCollection)) Make one from Array a ; # ; > (send c :at 1) Value at index 1 is 10 ; 10 ; > (send c :addFirst 7) Add to front of collection ; # ; > (send c :at 1) Index 1 is same spot ; 10 ; > (send c :removeLast) Remove from either end ; 1 ; > (send c :last) Last element is now 2 ; 2 ; > (send c :asArray) Convert back to an array ; # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE SORTEDCOLLECTION CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The SortedCollection class requires a sort function. The collection gets ; re-sorted whenever a new element is added. This is a subclass of ; OrderedCollection. ; Offset won't change for this class. (defclass SortedCollection (sortfcn) nil OrderedCollection) (defmethod SortedCollection :isnew (&optional (fcn '< )) (setq sortfcn fcn) (send-super :isnew)) (defmethod SortedCollection :selfSort () ; "private" method that sorts the list (setq data (sort data sortfcn)) self) (defmethod SortedCollection :add (arg) (send-super :add arg) (send self :selfSort)) ; Don't allow addFirst, addLast, removefirst, removelast, or atPut (defmethod SortedCollection :addFirst (arg) (send self :notImplemented :addFirst)) (defmethod SortedCollection :addLast (arg) (send self :notImplemented :addLast)) (defmethod SortedCollection :removeFirst (arg) (send self :notImplemented :removeFirst)) (defmethod SortedCollection :removeLast (arg) (send self :notImplemented :removeLast)) (defmethod SortedCollection :atPut (arg1 arg2) (send self :notImplemented :atPut)) ; We need a way to remove elements from a Sorted Collection. ; :remove (specifying the element) will do just fine. (defmethod SortedCollection :remove (arg) (let ((pos (position arg data))) ; Find (first) instance (when pos ; Delete found element (if (zerop pos) (setq data (cdr data)) (setf (cdr (nthcdr (1- pos) data)) (nthcdr (1+ pos) data)))) self)) ; Finally, storeon is modified so that the sort function will be set (defmethod SortedCollection :storeon () (list 'send (list 'send (intern (send (send self :class) :pname)) :new (list 'quote sortfcn)) :addAll (list 'quote (send self :asList)))) ; Let's see how the SortedCollection works: ; > (setq s (send c :asSortedCollection)) Sorted when it is created ; # ; > (send s :add 8) :add puts new element in order ; # ; > (send s :asSortedCollection #'>) New collection with order reversed ; # ; > (send (send (send Set :new) :addAll '(5 3 8 2 5 4 8)) :asSortedCollection) ; # Eliminate duplicates and sort ; > (send * :asList) ; (2 3 4 5 8) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; THE INTERVAL CLASS ; ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; The Interval class is considerably different than the others in that it has ; no "data" but calculates each elements value. We'll use three new ; instance variables -- start end and step. The :new function will take two ; or three arguments to specify start, end and step, with the step size ; defaulting to 1 not defined. We will set the data instance ; variable to T if the range is valid, and to NIL if not (no elements) (defclass Interval (start end step) nil SequenceableCollection) (defmethod Interval :isnew (arg1 arg2 &optional arg3) (if arg3 (setq data (or (and (<= arg1 arg2)(> arg3 0)) (and (>= arg1 arg2)(< arg3 0)))) (setq arg3 1 data (<= arg1 arg2))) (setq start arg1 end arg2 step arg3) ; Correct End value if necessary (unless (zerop (rem (- end start) step)) (setq end (- end (rem (- end start) step)))) ) ; :at calculates value. We won't check for out of range. (defmethod Interval :at (arg) (+ start (* step arg))) ; :atPut isn't allowed (defmethod Interval :atPut (arg1 arg2) (send self :notImplemented :atPut)) ; :size returns calculated size (defmethod Interval :size () (if data (1+ (truncate (- end start) step)) 0)) ; :includes must be calcuated (defmethod Interval :includes (arg) (cond ((null data) nil) ((> step 0) (and (>= arg start) (<= arg end) (zerop (rem (- arg start) step)))) (t (and (<= arg start) (>= arg end) (zerop (rem (- arg start) step)))))) ; While Collection bases :asList on :map, we want to base :map on ; :asList (defmethod Interval :map (fcn) (mapcar fcn (send self :asList))) (defmethod Interval :asList () (let ((result nil)) (when data (dotimes (i (send self :size)) (setq result (cons (+ start (* i step)) result)))) (nreverse result))) ; Since :do is used often with an Interval, and since the default method ; would create a list of values, it would make sense to reimplement :do ; here as an Interval method. That will be left as an exercise for the ; reader! ; :collect, :select will work because we will redefine :species to ; create an OrderedCollection rather than an Interval (defmethod Interval :species () OrderedCollection) ; Override printing methods (defmethod Interval :prin1 (&optional (stream *standard-output*)) (format stream "#<~a from ~s to ~s by ~s>" (send (send self :class) :pname) start end step)) ; Override :storeon -- this one becomes really easy (defmethod Interval :storeon () (list 'send 'Interval :new start end step)) ; A few examples of the use of the Interval class: ; > (setq i (send Interval :new 2 10 2)) Make an interval, i ; # ; > (send i :do (lambda (x) (format t "~s " x))) Demonstrate :do ; 2 4 6 8 10 ; nil ; > (send i :at 3) Check operation of :at ; 8 ; > (send i :size) Size of interval ; 5 ; > (send i :asList) Convert to a list ; (2 4 6 8 10) ; > (send i :asSortedCollection #'>) Convert to a SortedCollection ; # sequence changes! (in-package "USER") ; revert to default package (use-package "CLASSES") ; Make the classes package accessable