Category Archives: Uncategorized

The less-familiar parts of Lisp for beginners — remprop

Now, we’ll discuss remprop a bit.  You may recall an earlier article where I discussed the concept of Lisp symbols.  There, I talked about symbols having five fields, such as the function field or the value field.  I mentioned the “property list” field, but didn’t say anything more about it.  Now, the time has come to examine that.

Now, while a symbol can have an associated value field, it can also have an associated property list that allows it to hold a set of key/value pairs.  A property list is a list with an even number of entries, in which each successive pair of entries represents a key and associated value.  Conceptually, but not structurally, it resembles an association list.  The remprop function allows the user to remove the first pair whose key is eq to the value passed in the function’s second argument.  Note that there is no way to modify the test function, and the use of eq rather than equal means that the programmer should avoid assigning to the property list with keys that are numbers or characters.

Now, I haven’t found reasons to use property lists so far.  In other projects, they have been used to attach meaningful data to functions.  For instance, one might attach a callback function to a symbol associated with a particular function, rather than building a struct that contains the function and its associated callback.

One warning: do not think of property lists as another way to build slots in a structure, the property lists are bound to a symbol, not to an instance, and copying the value of a symbol does not copy its property list.

The less-familiar parts of Lisp for beginners — remove-method

Now we come to another Lisp feature with no real analogue in C++, the standard generic function remove-method.  What this function does is to undo the effect of a defmethod construct, at run time.  That is, the Lisp image can forget about a specialized method.  As we’ve noted several times in the past, this difference comes about because of a fundamental difference in the way Lisp and C++ runtime environments are handled.  In C++, you modify the source code, recompile the binary, and then restart the program.  In Lisp, the image itself contains the methods, functions, and data that any Lisp program might want to use, but it’s better not to think of the program as really existing, it’s more like a context that happens to be executing functions at the moment.

In fact, now that I think of it, a C++ programmer might better understand a Lisp “program” as a particular thread executing in a larger context.  The thread has an associated stack and local data, and runs through certain functions, and maybe eventually exits, but does all this in the context of a sea of functions and methods available to it.  Different threads can run entirely different “programs”, and as long as nobody starts messing around with global state, these programs don’t even know about one another.  So, think of your C++ program was really just a launching pad for these threads, and you have the ability to modify methods and classes on the fly then run a “program” thread using the updated binary image.  That’s a bit like how you can view the Lisp runtime environment.

So, remove-method removes a specialized method, as if the corresponding defmethod had never been loaded.  Here’s an example of this in use.  I create a base class and a derived class, each with different methods for do-something.  Then, I make an instance of the derived class, and I locate the method specialized on the derived class.  The first time I call do-something, it uses the method for the derived class.  Then, I call remove-method on that method, and call do-something again.  Now, with no specialized method for the derived class, the base-class method is called instead:
remove-method.lisp

(defclass base-class ()
  ())

(defclass derived-class (base-class)
  ())

(defgeneric do-something (obj))

(defmethod do-something ((obj base-class))
  (format t "Called do-something in the base class.~%"))

(defmethod do-something ((obj derived-class))
  (format t "Called do-something in the derived class.~%"))

(defun demonstrate ()
  (let ((obj (make-instance 'derived-class))
        (specialized 
         (find-method #'do-something
                      '()
                      (list (find-class 'derived-class)))))
    (do-something obj)
    (remove-method #'do-something specialized)
    (do-something obj)))

With output:
*slime-repl sbcl*
CL-USER> (demonstrate)
Called do-something in the derived class.
Called do-something in the base class.
NIL

The less-familiar parts of Lisp for beginners — reinitialize-instance

Now we come across another unusual feature of Lisp, the standard generic function reinitialize-instance.  This function is used to modify the local slots (in C++ terms, the non-static members) of an instance.  Now, its default behaviour isn’t especially interesting, as it behaves essentially like access through multiple calls to slot-value.  However, it can be specialized by the programmer, allowing him/her to take additional actions based on the initialization.  For instance, here is how one might code up a particular behaviour without reinitialize-instance:
reinitialize-instance.lisp

(defclass citizen ()
  ((age         :initform 0
                :initarg :age
                :accessor get-age)
   (minor       :initform t
                :initarg :minor
                :accessor get-minor)
   (voter       :initform nil
                :initarg :voter
                :accessor get-voter)
   (can-drink   :initform nil
                :initarg :drinker
                :accessor get-drinker)))

(defun year-goes-by (person)
  (case (incf (get-age person))
    (18
     (setf (get-minor person) nil
           (get-voter person) t))
    (21
     (setf (get-drinker person) t))))

(defun demonstrate ()
  (let ((person (make-instance 'citizen)))
    (dotimes (i 30)
      (format t
              "Age: ~D  Minor: ~A  Voter: ~A  Drinker: ~A~%"
              (get-age person) (get-minor person)
              (get-voter person) (get-drinker person))
      (year-goes-by person))))

Now, we write a new version with reinitialize-instance.  Note that the age-dependent rules are now bound in a method specialized on the class, not in an external function.  By replacing the accessors with readers, we force users to update the instance with reinitialize-instance (they can also use slot-value, which defeats this code, but we document the requirement that slot-value not be used):
reinitialize-instance-2.lisp

(defclass citizen ()
  ((age         :initform 0
                :reader get-age
                :initarg :age)
   (minor       :initform t
                :reader get-minor
                :initarg :minor)
   (voter       :initform nil
                :reader get-voter
                :initarg :voter)
   (can-drink   :initform nil
                :reader get-drinker
                :initarg :drinker))
  (:documentation "Never alter slots with slot-value, use reinitialize-instance with :age instead"))


(defmethod reinitialize-instance ((person citizen) &rest initargs)
  (let ((new-age (second (member :AGE initargs))))
    (when new-age
      (setf (slot-value person 'age) new-age)
      (cond
        ((< new-age 18)
         (setf (slot-value person 'minor) t
               (slot-value person 'voter) nil
               (slot-value person 'can-drink) nil))
        ((< new-age 21)
         (setf (slot-value person 'minor) nil
               (slot-value person 'voter) t
               (slot-value person 'can-drink) nil))
        (t
         (setf (slot-value person 'minor) nil
               (slot-value person 'voter) t
               (slot-value person 'can-drink) t)))))
  person)



         

(defun demonstrate ()
  (let ((person (make-instance 'citizen)))
    (dotimes (i 30)
      (format t
              "Age: ~D  Minor: ~A  Voter: ~A  Drinker: ~A~%"
              (get-age person) (get-minor person)
              (get-voter person) (get-drinker person))
      (reinitialize-instance person :age i))))


Finally, I’ll note that a common way to specialize reinitialize-instance is to write an :after method for it, not to replace the default function.  This allows the programmer to let the system apply its normal rules for replacing slot values, then the specialized :after method can swoop in and tweak slots as necessary.

The less-familiar parts of Lisp for beginners — reduce

Our next stop on the brief alphabetical tour through less commonly-used Lisp features is reduce.  This function is used to apply a binary function over a sequence.  The output of the function must be compatible with its input, as it feeds back into itself.

If the keyword argument from-end is nil (the default), then the function is applied first to the first two elements of the sequence.  The result of this function becomes the first input to the next invocation of the function, the second argument of which is the third element in the sequence.  The output again feeds into the function, with the second argument being taken from the fourth element in the sequence, until the sequence is exhausted.  If from-end is non-nil, the first call to the function takes arguments of the second-from-last and last elements of the sequence in that order.  Following invocations take the results of the previous function calls as the second parameter, and walk backwards through the sequence to obtain the first argument.

It is also possible to prime the function with an initial value, which becomes the first parameter to the first function call in the case where from-end is nil, and the second parameter to the first function call in the case where from-end is non-nil.

The edge cases are these:  If the sequence has no elements, and no initial value is supplied, the function is called with no arguments, and has to be able to handle that case.  If the sequence has exactly one element and no initial value is supplied, the function is not called, the single element’s value is returned.  If the sequence has no elements, but has an initial value, the function is not called, and the initial value is returned.  All other cases proceed according to the general case described above.  Also note that there are keyword arguments to select a subsequence, by choosing the start and/or end of the range within the sequence.

One way one might use this function is in applying a sequence of linear transformations with square matrix multiplications.  The matrices are queued up in a list, and then reduce is used to perform the multiplication to produce the net transformation matrix.  You can also use reduce on simple arithmetic operations like + and *, but note that it may be more efficient in those specific cases to use apply, as those operations take an arbitrary number of arguments already.

The less-familiar parts of Lisp for beginners — readtable-case

Now we examine the accessor readtable-case.  This feature reads or sets the upper/lower case behaviour of a readtable.  That behaviour affects how symbol names are generated from an input stream.

For instance, the startup readtable-case for the standard readtable in SBCL is :UPCASE.  If the REPL is given as input the string Hello, the symbol generated is HELLO.

Other possible behaviours are :DOWNCASE, :PRESERVE, and :INVERT.  The first two are self-explanatory.  The third one preserves the case for mixed-case strings, but inverts it for single-case strings.  So Hello remains Hello, while HELLO becomes hello and hello becomes HELLO.  This :INVERT behaviour is not seen in keyword symbols in SBCL 1.1.14:  :HELLO remains :HELLO.