Monthly Archives: May 2014

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.

The less-familiar parts of Lisp for beginners — read-line

Now, we come to the read-line function.  For the C++ programmer, this is like std::getline.  This function reads in the text stream until the next newline, if any, and returns the content in a string.  It does no interpretation of the text beyond searching for the newline, and does not care about the syntax of the text contents.  This is the function that you would be most likely to use when reading and scanning data files or natural language text.

The optional second argument, eof-error-p, defaults to true.  Unless this argument is set to nil by the programmer, read-line will raise an error if it encounters the end of line.  The optional third argument, eof-value, is that value that will be returned by read-line if the end of line is encountered before any other characters are read.  A common construct, then, is to call read-line with the stream and then two nil arguments, causing it to return strings containing lines of text until none remain, when it returns nil instead of a string.

The read-line function returns a second value, nil if the line read was terminated by and end of line, and true if it was terminated by the end of the file.  Using this, the programmer can determine whether or not  the last line in the file has a trailing newline.

The less-familiar parts of Lisp for beginners — read-from-string

Continuing through our list of less familiar features of Lisp, next is read-from-string.  The same caveats I mentioned under read apply here.  This is not something that you would typically use for a general-purpose input parser.  Like read, it requires that the input be fairly strongly constrained to match valid input that the Lisp REPL might encounter.  It is easy for it to encounter a character that will raise a condition, so tends to be used in places where the strings being interpreted are carefully generated in a *print-readably* format.