Monthly Archives: December 2013

The less-familiar parts of Lisp for beginners — declaim

The series of posts related to Lisp functions that the newcomer arriving from C++ might not have encountered continues with the declaim macro.

The declaim macro is generally used as a top-level form, and has the effect of modifying the behaviour of the compiler.  Common uses include setting optimization and debugging states, or declaring that a function ought to be inlined where it is invoked.  It might also be used to indicate that a top-level variable is to be made special.  You will have seen me using the optimization settings in some earlier code examples, for instance here.

The new programmer is likely to be interested in this primarily for the compiler optimizations.  Variables being declaimed as special have to do with dynamic vs. lexical binding, which might be a separate posting some time later.

It is important to note that it is unspecified whether the side-effects of declaim on compilation persist past what the C++ programmer would call a compilation unit.  That is, if you compile a file with certain optimization settings using declaim, and then later compile another file, one that does not declaim specific settings, it is implementation-dependent whether or not the impact of the declaim macro is visible in the second compilation unit.  This may be significant, particularly if you are writing library code.

The less familiar parts of Lisp for beginners — summary B & C

I’ve written a few posts about features of the Lisp language that a newcomer arriving from C++ might not have encountered.  I’m going through them alphabetically, so here is a summary page of the functions beginning with the letters ‘B’ and ‘C’:

block

byte

call-next-method

change-class

compute-applicable-methods

I’m skipping over several interesting functions because they’re more usefully described along with related functions that we haven’t yet encountered.  These include broadcast-stream-streams, compiler-macro-function, concatenated-stream-streams, copy-pprint-dispatch, copy-readtable, and copy-symbol.

The less-familiar parts of Lisp for beginners — compute-applicable-methods

Continuing with some less familiar parts of Lisp from the C++ programmer’s perspective, we come to compute-applicable-methods.  For the programmer, this is most commonly going to be used for insight into the method combination mechanism.  Recall our discussion of call-next-method, there is a set of rules that describes how the method to be invoked is chosen.  There is a default set of rules, but the programmer can also write his or her own combination rules for certain classes.  When the code encounters a generic function invocation, it will choose a method to execute on the arguments, using compute-applicable-methods.

The beginner programmer can use this function to understand how the method is chosen.  The function returns a list of methods, in precedence order.  Here is a demonstration of this use:
 

;; Demonstrate compute-applicable-methods


(defclass base-class ()
  ())

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

(defgeneric myprint (stream object)
  (:documentation "Prints to 'stream' some information about 'object'."))

(defmethod myprint (stream (object base-class))
  (format t "Entering base-class primary method.~%")
  (format t "Leaving base-class primary method.~%"))

(defmethod myprint :around (stream (object base-class))
  (format t "Entering base-class :around method.~%")
  (format t "Calling next method.~%")
  (call-next-method)
  (format t "Leaving base-class :around method.~%"))

(defmethod myprint (stream (object derived-class))
  (format t "Entering derived-class primary method.~%")
  (format t "Leaving derived-class primary method.~%"))

(defun demonstrate ()
  (let ((base (make-instance 'base-class))
        (derived (make-instance 'derived-class)))
    (format t "The applicable methods list on base-class for 'myprint is:~%")
    (format t "~A~%~%" (compute-applicable-methods #'myprint 
                                                   (list t base)))
    (format t "And now we run 'myprint on a base-class object:~%")
    (myprint t base)

    (format t "~%~%")

    (format t "The applicable methods list on derived-class for 'myprint is:~%")
    (format t "~A~%~%" (compute-applicable-methods #'myprint 
                                                   (list t derived)))
    (format t "And now we run 'myprint on a derived-class object:~%")
    (myprint t derived)))

Producing output as follows:
 
CL-USER> (demonstrate)
The applicable methods list on base-class for 'myprint is:
(#<STANDARD-METHOD MYPRINT :AROUND (T BASE-CLASS) {100CEC1243}>
 #<STANDARD-METHOD MYPRINT (T BASE-CLASS) {100CD87083}>)

And now we run 'myprint on a base-class object:
Entering base-class :around method.
Calling next method.
Entering base-class primary method.
Leaving base-class primary method.
Leaving base-class :around method.

The applicable methods list on derived-class for 'myprint is:
(#<STANDARD-METHOD MYPRINT (T DERIVED-CLASS) {100CF3AB63}>
 #<STANDARD-METHOD MYPRINT :AROUND (T BASE-CLASS) {100CEC1243}>
 #<STANDARD-METHOD MYPRINT (T BASE-CLASS) {100CD87083}>)

And now we run 'myprint on a derived-class object:
Entering base-class :around method.
Calling next method.
Entering derived-class primary method.
Leaving derived-class primary method.
Leaving base-class :around method.
NIL

You’ll notice that, for the derived class, it looks as if the order is wrong, because the base-class :around method was called, not the derived-class primary method.  Under method combination rules, if there are any :around methods, then the most specific :around method is invoked instead of any primary methods.  If that :around method invokes call-next-method, it then goes on through the rules we detailed before, under call-next-method.  So, the :around method, though second in the list of returned methods, overrides the more specific primary method.  Then, because of the call-next-method form, the more specific primary method is called.

The less-familiar parts of Lisp for beginners — change-class

Next in our series of posts the newcomer from C++ might have missed when starting Lisp, we have change-class.  I should point out here that I’ve been a bit sloppy when referring to “Lisp”.  The precise term should be “Common Lisp”, because the original Lisp didn’t come with an object system.  Nonetheless, I’m probably going to use the less precise term a lot.

So, change-class.  This is another one of those commands that has no parallel in C++.  It allows the programmer to convert an object of one class into one of a different class.  The classes do not even have to be related through a class hierarchy.  The change, of course, affects all references to the object, not just the view through the variable to which you applied change-class.

The default behaviour of change-class is to copy the contents of all like-named slots, and use :init forms to fill in the rest.  This, however, is usually not sufficient to meet the programmer’s needs, if all it does is to change the type of the object without significantly altering its contents.  To change the slots, or to fill in new slots based on values in old slots, you define a generic method of update-instance-for-different-class, specialized on the old and new types.  A good example of this operation appears in the CLHS.

Here is an example of code that demonstrates the basic behaviour of change-class.
 

;; Demonstrate change-class

(defclass my-base-class ()
  ((field-A     :accessor get-A
                :initform "Base class field A")
   (field-B     :accessor get-B
                :initform "Base class field B")))

(defclass my-derived-class-1 (my-base-class)
  ((field-C     :accessor get-C
                :initform "Derived class 1 field C")
   (field-D     :accessor get-D
                :initform "Derived class 1 field D")))

(defclass my-derived-class-2 (my-base-class)
  ((field-C     :accessor get-C
                :initform "Derived class 2 field C")
   (field-D     :accessor get-D
                :initform "Derived class 2 field D")))

(defclass unrelated-class ()
  ((field-A     :accessor get-A
                :initform "Unrelated class field A")
   (field-B     :accessor get-B
                :initform "Unrelated class field B")
   (field-C     :accessor get-C
                :initform "Unrelated class field C")))

(defmethod update-instance-for-different-class :before ((old my-derived-class-1) (new my-derived-class-2) &key)
  (setf (slot-value new 'field-C) "Overridden converted string."))

(defun demonstrate-1 ()
  (let* ((obj-1 (make-instance 'my-derived-class-1))
         (obj-1-other-reference obj-1))

    (format t 
            "Starting with an object of type ~A.~%"
            (type-of obj-1))
    (format t 
            "Its 'C' and 'D' fields are ~S and ~S~%"
            (get-C obj-1)
            (get-D obj-1))

    (format t
            "We convert it with change-class.~%")

    (change-class obj-1 'my-derived-class-2)

    (format t
            "It now has type ~A.~%"
            (type-of obj-1))

    (format t 
            "Its 'C' and 'D' fields are ~S and ~S~%"
            (get-C obj-1)
            (get-D obj-1))

    (format t 
            "And another reference to the object has type ~A~%"
            (type-of obj-1-other-reference))))

(defun demonstrate-2 ()
  (let ((base-obj (make-instance 'my-base-class)))
    (format t 
            "Created an object of type ~A~%"
            (type-of base-obj))

    (format t
            "Converting to an entirely new, unrelated class.~%")

    (change-class base-obj 'unrelated-class)

    (format t 
            "Object now has type ~A~%"
            (type-of base-obj))

    (format t
            "It's A and C fields are ~S and ~S~%"
            (get-A base-obj) (get-C base-obj))))

Executing this produces the following output:
 
CL-USER> (demonstrate-1)
Starting with an object of type MY-DERIVED-CLASS-1.
Its 'C' and 'D' fields are "Derived class 1 field C" and "Derived class 1 field D"
We convert it with change-class.
It now has type MY-DERIVED-CLASS-2.
Its 'C' and 'D' fields are "Overridden converted string." and "Derived class 1 field D"
And another reference to the object has type MY-DERIVED-CLASS-2
NIL
CL-USER> (demonstrate-2)
Created an object of type MY-BASE-CLASS
Converting to an entirely new, unrelated class.
Object now has type UNRELATED-CLASS
It's A and C fields are "Base class field A" and "Unrelated class field C"
NIL

The less-familiar parts of Lisp for beginners — call-next-method

Our next function for newcomers to Lisp is call-next-method.  This may or may not appear in an introduction to Lisp’s object-oriented system, CLOS.  You might want to review this earlier post for an introduction, including an example of the use of call-next-method.

The call-next-method function causes the program to call another method in the combination of the class.  Recall that, in Lisp, we have generic functions, which you can think of as being like the non-member functions of C++.  There is no equivalent to the member functions of C++.  Still, as these generic functions can be specialized on different classes in an inheritance hierarchy, we have polymorphism based on object type.  This function, in its simple usage, allows you to invoke functions in parent classes.  Where, in C++, you explicitly supply the name of the parent class whose method you want to invoke, in Lisp, by default, it searches out the next less specific method and calls that.

When a class does not inherit from multiple base classes, it is simple to use the default method combination rules.  It is also possible, of course, to define custom method combination rules when necessary, but I’m only going to deal with the defaults here.  You can see the default rules at this page in the CLHS.  Basically, it works as follows:  if there are one or more :around methods, the most specific one is executed, instead of calling the normal methods.  Typically, an :around method calls call-next-method to invoke the primary method, because otherwise the primary method is skipped entirely.  However, if there is a less specific :around method, this is called instead, and so on until there are no remaining :around methods, in which case call-next-method invokes, in order:

  • All of the :before methods in order, from most to least specific.
  • The most specific primary method (which can, itself, call call-next-method to invoke less specific primary methods).
  • All of the :after methods in order, from least to most specific (note, this is opposite to the order of the :before methods).

If call-next-method is called in a primary method, it invokes the next less specific primary method (if any).  The call-next-method function cannot be called from within a :before or :after method.

Check the documentation for details on how the presence or absence of arguments to call-next-method affects the parameters passed to the invoked method.

If the default method combination method is unsuitable, for instance if you want a particular class to call a method specialized on the grandparent class and skip over the method specialized on the parent class, that can be done by modifying the default with define-method-combination, which we will discuss later.