Monthly Archives: April 2014

The less-familiar parts of Lisp for beginners — notinline

Continuing the discussion of less familiar parts of Lisp for newcomers from C++, we’ll discuss the notinline declaration a bit.  This pairs up with inline, of course, but we’ve saved this discussion until now because of the context I wanted to introduce first, particularly as relates to symbols and the manipulation of their function cells.

By now, you’ve noticed that Lisp function calls involve execution-time symbol resolution.  While you’re probably familiar with load-time linking in C++ programs, things are a bit different in Lisp, because it is legal to change the function definition associated with a symbol, even while a program is running.  Perhaps, then, you’ve wondered about inline functions in Lisp.  How can you inline a function when the very definition of the function might change from one call to the next?

In C++, the compiler has fairly broad discretion on inlining functions.  The programmer may also hint that inlining is desirable for a particular function, with the inline keyword.  Some compilers recognize options that ask them to inline any function that is “reasonable” to inline, subject to the implementor’s definition.

In Lisp, the inline declaration tells the system that it is allowed and encouraged to inline this function, until cancelled by a notinline declaration.  Note that the Lisp implementation is free to ignore this, and is not required to have the ability to inline.  If a function attached to an interned symbol is inlined, and the function definition is later changed, those areas where the old form was inlined are not changed.  This can lead to confusion in an active development context, or when manipulations of the symbol table are made, such as a package import that shadows the function in question.  One way to avoid this confusion is to try to confine your use of inline to non-interned functions such as those created with labels or flet.

Here, then, is a transcript showing the effect of inlining:
 

CL-USER> (declaim (inline function-1))
; No value
CL-USER> (defun function-1 ()
           (format t "This is the old function-1~%"))
FUNCTION-1
CL-USER> (defun function-2 ()
           (format t "In function-2, calling by symbol:~%~T")
           (funcall 'function-1)
           (format t "In function-2, calling by function:~%~T")
           (funcall #'function-1))
FUNCTION-2
CL-USER> (declaim (notinline function-1))
; No value
CL-USER> (function-2)
In function-2, calling by symbol:
 This is the old function-1
In function-2, calling by function:
 This is the old function-1
NIL
CL-USER> (setf (fdefinition 'function-1)
               (lambda () 
                 (format t "This is the new function-1~%")))
#<FUNCTION (LAMBDA ()) {10036FEE7B}>
CL-USER> (function-2)
In function-2, calling by symbol:
 This is the new function-1
In function-2, calling by function:
 This is the old function-1
NIL

You can see that function-1 was declared inlineable, and then we defined function-2, which invokes function-1.  I invoke function-2, and nothing unexpected happens.  Then, I change the function bound to the function-1 symbol (see the earlier article on fdefinition if this is unclear).  Now, when I invoke function-2, I get both the old and new forms of function-1 appearing.  Why is that?  If you recall our discussion at fboundp, the single-quote prefix tells funcall, at run-time, to look up the function bound to the symbol.  On the other hand, the #’ prefix tells the reader to replace the content with the function expansion of the symbol.  Since the function is inlineable at the time the reader sees this form, the definition of function-1 is inserted into the code at that point, and persists even if that definition is later modified or deleted.  So, this is confusing, try not to do it.

The less-familiar parts of Lisp for beginners — notany

Onward through our list of less familiar parts of common Lisp.  I had originally skipped every, but I think I’d like to bring it up, along with its related functions, just to point out something helpful.  The functions every, some, notevery, and notany may look redundant in the context of Lisp’s and and or, but they do have some uses by virtue of the fact that they are functions.

Macros are useful, but there are some places they cannot go.  You cannot pass a macro to mapcar, or funcall, or apply.  While that is inconvenient, the and and or macros of Lisp have the familiar short-circuiting behaviour of && and || in C++.  The notany function and its relatives are functions, and so do not short-circuit, all of their arguments are evaluated whether or not there is an intermediate result that conclusively establishes the return code.

The reason I bring this up is that newcomers might not have noticed the way these functions can be used in mapcar contexts and the like, to achieve the effect of determining, for instance, whether a list of forms are all true.  Let’s look at the and case:
 

CL-USER> (let ((num 10))
           (funcall 'and (numberp num) (> num 9)))
; Evaluation aborted on #<UNDEFINED-FUNCTION AND {1003E5B0F3}>.
CL-USER> (let ((num 10))
           (funcall 'every 'identity (list (numberp num) (> num 9))))
T

As you see, the funcall failed because and, being a macro, cannot be invoked in this manner.

Note that this example is only an example.  The particular way I’ve laid this out is for illustrative purposes, and if we were really intent on making this particular test, we would use ‘and as follows:
 

CL-USER> (let ((num 10))
           (and (numberp num)
                (> num 9)))
T

In fact, the example I give above with funcall and every would be wrong in real code, because both the numberp and the > operations would be evaluated up front, and if the variable num did not hold a number, then the comparison operator would error out.  This is only to show, in a simple context, how notany and its relatives, being functions, might be used in places where macros are illegal.

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

The next-method-p local function must only be called from within a method definition.  It allows a method to determine whether there is a next method that might be available to call-next-method.  This is helpful, as invoking call-next-method without a next method available will invoke the no-next-method generic function which, unless overridden by a user-supplied method, signals an error.

The less-familiar parts of Lisp for beginners — nconc

We next come to the nconc function.  The quick way to think of this is like append, but modifying its lists rather than creating a new list with the elements of the input lists.  The difference is quite important, and merits some discussion.

Recall that lists in Lisp are what a C++ programmer would call NULL-terminated singly-linked lists.  Each cell in the list consists of two parts, a value part (the car), which can hold any data type, including other lists, and the next-pointer part (the cdr).

When the append function is called on, say, two lists, then a third list is created, one whose value parts of the cells are the same as those of the input lists, but whose next-pointer parts are unique to this new list, as they point to new cells that were created to support the new list.

When the nconc function is called on two lists, the NULL-termination of the first list is changed to point at the head of the second list.  This means that the original list is modified.  The programmer must also take care to avoid the unintentional creation of circular list structures, as these require some careful coding to handle.  Constructs like dolist and mapcar will, left to themselves, loop forever when the list is circular.

Here is some sample code to demonstrate the difference:
 

CL-USER> (defparameter *list-a* (list 1 2 3))
*LIST-A*
CL-USER> (defparameter *list-b* (list 4 5 6))
*LIST-B*
CL-USER> (append *list-a* *list-b*)
(1 2 3 4 5 6)
CL-USER> *list-a*
(1 2 3)
CL-USER> *list-b*
(4 5 6)
CL-USER> (nconc *list-a* *list-b*)
(1 2 3 4 5 6)
CL-USER> *list-a*
(1 2 3 4 5 6)
CL-USER> *list-b*
(4 5 6)
CL-USER> (setf *print-circle* t)
T
CL-USER> (nconc *list-a* *list-b*)
(1 2 3 . #1=(4 5 6 . #1#))
CL-USER> (dolist (var *list-a*)
           (format t "~A " var)
           (sleep 0.2))
1 2 3 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 ; Evaluation aborted on NIL.

In the final example, I used the Slime Interrupt Command key sequence CTRL-C CTRL-C, as otherwise the code would have run forever.  The special variable *print-circle* is defined in the standard as allowing the printing of circular objects such as the list we created above.  It uses a different code path to detect cycles and print them in a standard-defined way.

The less familiar parts of Lisp for beginners — summary M

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 letter M:

macro-function

macrolet

make-broadcast-stream

make-condition

make-dispatch-macro-character

make-instances-obsolete

make-load-form

make-load-form-saving-slots

make-method

make-package

make-symbol

makunbound

map

mask-field

method-qualifiers

muffle-warning

multiple-value-call

multiple-value-list

multiple-value-prog1

multiple-value-setq