Category Archives: Uncategorized

The less-familiar parts of Lisp for beginners — special-operator-p

In Common Lisp there are several operators that are referred to as “special”.  A special operator is one which has different behaviour in terms of bindings or flow control.  The list can be found, for instance, here.  The programmer cannot create their own special operators, there is no way to create a new entity for which special-operator-p returns non-nil.

Some examples will, perhaps, clarify what this means.  The Common Lisp if special operator takes two or three arguments.  The first is the condition, the second is evaluated only if the condition evaluates to non-nil, and the optional third argument is evaluated only if the condition evaluates to nil.  Hidden within the if form, there is a flow control that decides whether or not to evaluate certain arguments.

In the context of bindings, catch sets up a dynamic binding that becomes important if code below it invokes throw.  The labels special operator sets up a lexical binding within its scope.

The special-operator-p function returns non-nil if the symbol passed as its argument corresponds to a special operator.  Now, when might the programmer need to use this?  Most obviously in a code walker, something that reads and analyses Lisp code.  I have never seen it used other than in informational contexts, to label a particular piece of code as containing a special operator, or by the implementation to forbid the redefinition of such operators.  I haven’t seen a context in which complex actions are initiated by special-operator-p returning non-nil, only messages, errors, or skipping over a block while analysing code.

The less-familiar parts of Lisp for beginners — special

We’ve hinted a few times at special variables, but I’ll put it here in one spot to make it easier to find.  The special declaration is used, for instance in declaim or declare, to indicate that a variable should have dynamic binding.

The most important thing to realize about a dynamic variable is that it behaves as if implemented by a push-down stack.  Declaring a new local variable with the same name, for instance with let, creates a new dynamic extent that is visible in any called functions.  Modifications to the value of the variable are visible until the dynamic extent is exited, at which point the next enclosing extent becomes the current one.

It is a common convention that special variables have variable names that begin and end in an asterisk, but this is not typically enforced by the implementation.  Variables created with defvar or defparameter are always dynamic.

Here’s some simple code to demonstrate the behaviour:
special.lisp

(defparameter *myvar* 10)

(defun printvar ()
  (format t "*myvar*= ~D~%" *myvar*))

(defun incvar ()
  (incf *myvar*))

(defun demonstrate ()
  (format t "Outermost dynamic extent:~%")
  (printvar)
  (format t "Incrementing the value~%")
  (incvar)
  (printvar)
  (format t "Creating a new dynamic extent with value 15:~%")
  (let ((*myvar* 15))
    (printvar)
    (format t "Incrementing the value~%")
    (incvar)
    (printvar))
  (format t "Exiting this new dynamic extent~%")
  (printvar))
          

with output:
*slime-repl sbcl*
CL-USER> (demonstrate)
Outermost dynamic extent:
*myvar*= 10
Incrementing the value
*myvar*= 11
Creating a new dynamic extent with value 15:
*myvar*= 15
Incrementing the value
*myvar*= 16
Exiting this new dynamic extent
*myvar*= 11
NIL

The less-familiar parts of Lisp for beginners — slot-value

At this point in our walk through less commonly seen features of Lisp we come to the slot-value function.  This is a good place to reiterate some of the differences between the way C++ and Lisp objects are handled.

In C++, a class will typically have private and public methods.  The public methods form the user-facing API, while the private methods are used for internal implementation, and cannot be used by non-friend classes or functions.  The same goes for private and public members, other classes or functions don’t have access to private member objects.  Friends of a class are declared in the class definition, as are the public/private modifiers, so it is not possible for another function or class to manipulate the private members or methods of a class without changing the class definition itself.

In Lisp, the private/public distinction is still present, but it is not as strictly enforced.  Where C++ has public and private methods on a class, Lisp has external and internal symbols on a package.  In C++ one typically writes methods referred to as “getters” and “setters” for a member in a class, while a class in Lisp can have :reader, :writer, or :accessor methods for individual members.

So, I mentioned that Lisp does not enforce the private/public distinction as strictly as C++.  In Lisp, it is possible to use the internal symbols of a package, one has merely to use a double colon between the package name and the symbol name, rather than the usual single colon used for external symbols.  This makes it immediately obvious that the code is using symbols that are not part of the API, but means that the programmer does not have to modify the other package to cause it to export that symbol.  The equivalent for accessors is slot-value.  This function allows the programmer to read or modify (with setf), any named slot in an instance of a class, regardless of whether or not there are accessors associated with it.

When a programmer finds him or herself using internal symbols from packages, or most uses of slot-value, it’s a sign that some examination of the context may be required.  The maintainer of the package probably feels free to modify the internal symbols of his or her package without worrying about backward compatibility, as it’s not part of the API, so other code that makes use of those internal symbols may break if the package is upgraded.  Further, unlike C++, where the friends of the class are listed in the class definition, Lisp provides no similar method for the programmer changing the package to know where some code somewhere might be making use of internal symbols.

Similarly, manipulation of slots of an instance through slot-value often indicates that the programmer did not think such manipulation wise, perhaps because changes to it must accompany other changes for consistency, so a naive poking about in the innards may have unexpected results.

There are, however, some common use cases for slot-value that are expected and not suggestive of problems.  The first is exactly in the case mentioned at the end of the previous paragraph.  When a method changes two or more slots together, you do not want :writer methods that allow the manipulation of those slots individually.  You might create :reader methods, and then use slot-value with setf to change the slots together in a single method.  A second case is in setting what a C++ programmer would call a const member object.  Once more, the programmer doesn’t want to create a :writer or :accessor method for the slot, to discourage others from changing it, but that slot does have to be set during instance creation.  There, the use of slot-value is natural and preferred.

The less-familiar parts of Lisp for beginners — slot-unbound

The slot-unbound standard generic function is invoked when an attempt to read an unbound, but existing slot in an instance is made (if this explanation is unclear, please review the earlier article on slot-boundp).  Once more, this function is not for the programmer to call into, but by specializing this function it is possible to take special action when an attempt is made to read the contents of an unbound slot.

In effect, slot-unbound has the same relationship to slot-boundp that slot-missing has to slot-exists-p.

The less-familiar parts of Lisp for beginners — slot-missing

The slot-missing generic function is a function that is not intended to be invoked by the programmer, but it’s still interesting to the programmer in some contexts.  This generic function is invoked by the Lisp system when an attempt is made to retrieve from an instance the value of a slot that doesn’t exist in the class.

By specializing this function, the programmer can attach particular actions to such invalid access attemps.  The ways to access a slot by name in an object are slot-value (with or without its setf operator), slot-boundp, and slot-makunbound.  The invocation of slot-missing allows the programmer to distinguish between these cases.  One possible use would be to add debugging information or logging when one of these invalid accesses is attempted.