Monthly Archives: December 2013

The less-familiar parts of Lisp for beginners — byte

On to more obscure Lisp commands.  The byte command is used to generate a bitrange specifier.  This can then be passed to some other function like ldb or dpb so that manipulations can be made to individual bits within an integer.  This is perhaps best shown with an example:
 

CL-USER> (let ((b1 (byte 16 0))
               (b2 (byte 16 1)))
           (format t "Byte 16 0:  #x~X~%" (dpb #x01234567 b1 0))
           (format t "Byte 16 1:  #x~X~%" (dpb #x01234567 b2 0))
           (format t "Byte 16 1 / 2:  #x~X~%" (/ (dpb #x01234567 b2 0) 2)))
Byte 16 0:  #x4567
Byte 16 1:  #x8ACE
Byte 16 1 / 2:  #x4567

What this sequence says is that we define b1 to be an interval of 16 bits, shifted 0 bits from the right (and so enclosing the 16 least-significant bits in the integer).  We define b2 to be an interval of 16 bits, but shifted by 1 bit from the right, so it encloses the 16 bits immediately above the least-significant bit.  We’ve then used dpb to deposit the least significant 16 bits of the hexadecimal literal #x01234567 into the integer 0.  With b1, it inserts into the least significant bits of the number 0, so the effect is to copy #x4567 into the least-significant position of a number that otherwise has all bits zero.  With b2, the insertion is one bit to the left, producing a new sequence #x8ACE which is twice #x4567, because it is shifted one bit to the left.

The less-familiar parts of Lisp for beginners — block

Continuing the series, another Lisp directive that newcomers arriving from C++ might not immediately encounter is block.  This is a simple flow-control statement which can almost be emulated using a goto in C++.

What block does is to create a named collection of statements, such that flow can immediately exit the block when return-from is used.  In C++, you could imagine putting a label immediately after the last statement in the Lisp block, and then using goto to branch to that label.  This is not quite an exact parallel because a Lisp block can return a value, while that concept doesn’t apply in C++.

In Lisp, a block that exits normally returns the result of its last statement.  In the event of a branch with return-from, an optional second argument is the return value of the block, otherwise the block returns nil.

Note that the Lisp return statement does not take a name as an argument, and returns from the innermost enclosing unnamed (nil) block, whether named or not.  This might be a block you created with the block directive, or it might be an implicit block surrounding one of the looping constructs, so for safety you’re encouraged to use named blocks and return-from when that is appropriate.  When using a looping construct like do, dolist, or dotimes, a bare return reads like a break in C++.

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

Continuing our series of posts on the more obscure commands of Lisp that beginners arriving from C++ might not have encountered and might not know when to use, we come to allocate-instance.

Think of this command as something that returns an object that hasn’t been constructed/initialized.  It’s an operation that has no counterpart in C++.  The object is built without filling in any of its slots.

Now, this may seem like a somewhat unnecessary function.  After all, why not just build the object with its slots initialized, and then overwrite the slots in a separate operation?  Well, there can be cases where allowing the object to be built normally, with make-instance, is undesirable.  A simple example might be the following:
 

;; A situation where you might prefer allocate-instance to
;; make-instance.


(let ((number-seq 0))

  (defun get-next-seq-id ()
    (incf number-seq)))

(defclass myclass ()
  ((id          :reader get-id
                :initform (get-next-seq-id))
   (data        :accessor get-data)))

(defun demonstrate ()
  (let ((obj1 (make-instance 'myclass))
        (obj2 (make-instance 'myclass))
        (obj3 (make-instance 'myclass)))

    (format t "ID of obj1= ~D~%" (get-id obj1))
    (format t "ID of obj2= ~D~%" (get-id obj2))
    (format t "ID of obj3= ~D~%" (get-id obj3))))

So, let’s say you’ve built this system, and you have some need to construct a new object which reuses an earlier ID.  Perhaps there was a bug that showed up only when a specific ID was passed into your hashing function, and you need to replicate the bug, but the object no longer exists.  As set up, a call to make-instance results in the next number in the sequence being allocated, and there’s no way to prevent the ID allocator from incrementing its internal state.

By using allocate-instance, you create a new object without calling its initform slot options.  Of course, you can imagine other contexts where avoiding the initform options are desirable, such as if the initform is very time-consuming, or if it invokes operations that affect the state in some way, such as opening network connections or disc files.

One place where I have seen allocate-instance used is in a generic object serializer in the cl-containers package.  There, objects can be passed to a method that writes them out to disc, and later they can be read back in.  To avoid complications during the read-in, the objects are allocated with allocate-instance and then the slots are individually restored from the disc file using a (setf (slot-value …) …) construct.

The less-familiar parts of Lisp for beginners — adjust-array

Continuing this series of posts on less familiar parts of Lisp that C++ programmers might not have encountered when starting with Lisp, we have adjust-array.

I’m only going to talk about this in the absence of displaced arrays.  We’ll get to a discussion of displaced array later in this series.  For now, we’ll just describe a simple use of adjust-array.

This function is used to change the dimensions of an array (but not the rank of the array, i.e. the number of dimensions).  When applied, adjust-array either modifies the array or returns a new array whose contents are derived from the contents of the array being adjusted.  If when the original array was created, it was marked as adjustable, then adjust-array modifies the array, otherwise it returns a new array.  Note that, if the input array was not adjustable, and a new array is created, the original array is destructively modified.

For each index in the array, if the new size of that dimension is smaller, elements that are no longer spanned by the new dimensions are absent from the adjusted array.  If the new size of that dimension is larger, new elements are inserted into those positions that did not exist in the input array.  If a particular n-tuplet of indices exists in both the input array and the adjusted array, then the contents of both arrays will be the same there.

To visualize this in two dimensions, imagine that you’ve laid out the array, populating its cells.  You then overlay a second array of the same rank but different dimensions.  Some rows or columns may be absent in this second array, and those elements will not appear in the second array.  Some rows or columns may be present in the second array but not in the first, in which case the new elements will be filled in with a default value.  Where the arrays overlap, the elements are the same.

Here are some examples using both a non-adjustable array and an adjustable array.  The original array is filled with a number sequence, and the adjustment is told to fill in newly-available array slots with the number 20.
*slime-repl sbcl*  

CL-USER> (let ((original-array 
                (make-array '(3 3) 
                            :initial-contents '((1 2 3)
                                                (4 5 6)
                                                (7 8 9))
                            :adjustable nil)))
           (format t "original-array before adjust= ~A~%"
                   original-array)
           (let ((new-array (adjust-array original-array
                                          '(2 4) 
                                          :initial-element 20)))
             (format t "original-array after adjust= ~A~%"
                     original-array)
             (format t "new-array= ~A~%" new-array)
             (format t "Arrays are same object= ~A~%" 
                     (eq new-array original-array))))

original-array before adjust= #2A((1 2 3) (4 5 6) (7 8 9))
original-array after adjust= #2A((1 2 3) (20 4 5) (6 20 9))
new-array= #2A((1 2 3 20) (4 5 6 20))
Arrays are same object= NIL
NIL
CL-USER> (let ((original-array 
                (make-array '(3 3) 
                            :initial-contents '((1 2 3)
                                                (4 5 6)
                                                (7 8 9))
                            :adjustable t)))
           (format t "original-array before adjust= ~A~%"
                   original-array)
           (let ((new-array (adjust-array original-array
                                          '(2 4) 
                                          :initial-element 20)))
             (format t "original-array after adjust= ~A~%"
                     original-array)
             (format t "new-array= ~A~%" new-array)
             (format t "Arrays are same object= ~A~%" 
                     (eq new-array original-array))))
original-array before adjust= #2A((1 2 3) (4 5 6) (7 8 9))
original-array after adjust= #2A((1 2 3 20) (4 5 6 20))
new-array= #2A((1 2 3 20) (4 5 6 20))
Arrays are same object= T
NIL