Monthly Archives: August 2014

The less-familiar parts of Lisp for beginners — unuse-package

The unuse-package function modifies the Lisp image, causing external symbols from one or more packages that have been inherited into another package (by the use of the complementary function use-package) to stop being available by inheritance within that other package.  This is fairly straight-forward, but it is important to note that this function affects only symbols inherited through use-package, it does not affect symbols explicitly brought in with import.

The less-familiar parts of Lisp for beginners — typecase

Next, we look at the typecase macro.  As Lisp does not enforce types of variables, it is valid for different code paths to set a particular variable or parameter to different data types.  The programmer may want to write code to handle all the cases.  Whereas in C++ one typically would overload a function based on its parameter types, with the compiler assigning the correct function at compile time, in Lisp one would branch code paths based on the run-time determined parameter type.

Now, in C++, it’s common for one function to be the actual worker, and the other functions to be thin wrappers around it.  The non-worker functions simply convert the arguments into a form suitable for processing by the single worker function.  This avoids unnecessary duplication of code and mysterious inconsistencies when one function is modified or debugged while leaving the other functions alone.  In Lisp, the similar construct is to recurse into itself with the modified types.  Here is an example:
typecase.lisp

(defstruct moments 
  (sum-x        0.0d0)
  (sum-x2       0.0d0))

(defun add-values (moment-struct &rest to-add)
  (dolist (one-to-add to-add)
    (typecase one-to-add
      (number
       (incf (moments-sum-x moment-struct) one-to-add)
       (incf (moments-sum-x2 moment-struct) (* one-to-add
                                               one-to-add)))
      (cons
       (dolist (entry one-to-add)
         (add-values moment-struct entry)))
      (vector
       (dotimes (i (length one-to-add))
         (add-values moment-struct (aref one-to-add i))))))
  moment-struct)
                    

The output from a typical set of calls:
*slime-repl sbcl*
CL-USER> (add-values (make-moments) '((1 2) (3 (4 5)) 6))
#S(MOMENTS :SUM-X 21.0d0 :SUM-X2 91.0d0)
CL-USER> (add-values (make-moments) '((1 2) (3 (4 5)) 6 nil))
#S(MOMENTS :SUM-X 21.0d0 :SUM-X2 91.0d0)
CL-USER> (let ((ones-vec (make-array 5 :initial-element 1)))
           (add-values (make-moments) ones-vec))
#S(MOMENTS :SUM-X 5.0d0 :SUM-X2 5.0d0)

You’ll note that the add-values function I’ve written ignores any arguments that are not list, vector, or numeric.  It may be desirable, instead, to use ctypecase or etypecase to signal correctable or non-correctable errors, respectively, when an unexpected type is passed.  That is typically the way I code such constructs, I prefer programs to error out than to silently ignore data passed to them, but how one wants to do this will depend on context and the requirements of the code.

Finally, I’ll point out one other place where I’ve used typecase in the past, when writing macros.  You may have seen an earlier series of posts that I put up on creating macros for a doubly-linked list structure related to my work.  In my context, I needed an additional parameter on the looping macro, for the direction.  The code had to behave slightly differently for forward and backward looping, specifically when building the increment function and loop exit condition.  When the direction parameter was :FORWARD, some code was active, and when :REVERSE, other code was active.  Often, that parameter was explicit in the code, sometimes it was the value of a symbol at runtime.  When the compiler encounters a literal :FORWARD during macro expansion, it can determine that the reverse code path cannot be executed, and issues a warning about unreachable code.  I don’t like my working code to generate warnings, they clutter the output and obscure problems.  So, to avoid the warnings when literal keywords are used, I used typecase at macro expansion time.  Here’s a piece of that code.  You’ll note that the typecase is not in a backtick, so it is evaluated at macro expansion time.  If the compiler determines that the direction parameter is a keyword, it inserts the appropriate single piece of code.  If it is not a keyword, macro expansion falls through to the lower block which evaluates the direction parameter at runtime and then invokes the appropriate macro expansion.
typecase.lisp

(defmacro iter-loop-open-interval ((dll iter start end 
                                        &key (dirxn :FORWARD))
                                   &body body)
  "Loop through the dll while 'iter' ranges from from start+1 to end-1."
  (let (first increment last)
    (typecase dirxn
      (keyword
       (ecase dirxn
         (:FORWARD
          (setf first `(dl-list:iter-dir ,dll 
                                         ,start 
                                         :PLUS 
                                         :circular t))
          (setf increment `(iter-dir ,dll 
                                     ,iter 
                                     :PLUS 
                                     :circular t))
          (setf last `(dl-list:iter-dir ,dll 
                                        ,end 
                                        :MINUS 
                                        :circular t)))
         (:REVERSE
          (setf first `(dl-list:iter-dir ,dll 
                                         ,start 
                                         :MINUS 
                                         :circular t))
          (setf increment `(iter-dir ,dll 
                                     ,iter 
                                     :MINUS 
                                     :circular t))
          (setf last `(dl-list:iter-dir ,dll 
                                        ,end 
                                        :PLUS 
                                        :circular t))))

       `(unless (or (eq ,start ,end)
                    (eq ,start ,last))
          (do ((,iter ,first ,increment))
              ((eq ,iter ,end))
            ,@body)))

      (t
       `(ecase ,dirxn
          (:FORWARD
           (iter-loop-open-interval (,dll ,iter ,start ,end 
                                          :dirxn :FORWARD)
                                    ,@body))
          (:REVERSE
           (iter-loop-open-interval (,dll ,iter ,start ,end 
                                          :dirxn :REVERSE)
                                    ,@body)))))))
  

The less-familiar parts of Lisp for beginners — throw

I’m going to talk a bit now about catch and throw.  These aren’t really obscure, particularly to somebody coming from a C++ background, who probably goes looking for these keywords when wanting to find out about throwing exceptions.  I talked at length about exceptions in Lisp earlier, and skipped over these, as they’re not really interesting or novel, but they deserve a mention.

So, catch and throw.  Similar in concept to the operations in C++, but rather than the catch specializing on objects, catch specifies a label, and throw jumps to the named label.  Consequently, you don’t have cascading catch directives, as you might have in C++.  If there is no matching catch for the throw, an error is signaled.

The less-familiar parts of Lisp for beginners — the

The newcomer to Lisp might not have seen the special operator the before.  This special operator allows the programmer to inform the compiler of the type returned by a form, in the event that the compiler cannot deduce it from context.  Note that this is not the same as a C or C++ static cast, the does not modify a type, and does not necessarily perform type checking (behaviour is undefined if you present it an object that is incompatible with the type declared by the), it merely tells the compiler what it should expect to get back from a form.  Compare this with the declare special operator, which can be used to tell the compiler what the types are of parameters passed to a function.

To demonstrate, I’ve written a function, remote-function, that simply returns the number 20.  I then have some code that uses this function, which I’m compiling for speed.  The compiler doesn’t know what remote-function does, and I could always change the function after compilation, so it can’t optimize fully without the the special operator.  The resulting disassembly listings show the difference.  In the first case, without the, the addition operator is a function call to generic-+, whereas in the second case the addition is made using straight integer arithmetic, with no function setup and call.

Without the special operator compiler hint:
the.lisp

(declaim (optimize (debug 0) (safety 0) (speed 3)))

(defun my-adder ()
  (+ 5 (remote-function)))

*slime-repl sbcl*
CL-USER> (disassemble 'my-adder)
; disassembly for MY-ADDER
; Size: 51 bytes
; 033CEADF:       488D5424F0       LEA RDX, [RSP-16]          ; no-arg-parsing entry point
;      AE4:       4883EC18         SUB RSP, 24
;      AE8:       488B05A9FFFFFF   MOV RAX, [RIP-87]          ; #<FDEFINITION object for REMOTE-FUNCTION>
;      AEF:       31C9             XOR ECX, ECX
;      AF1:       48892A           MOV [RDX], RBP
;      AF4:       488BEA           MOV RBP, RDX
;      AF7:       FF5009           CALL QWORD PTR [RAX+9]
;      AFA:       480F42E3         CMOVB RSP, RBX
;      AFE:       BF0A000000       MOV EDI, 10
;      B03:       41BBF0010020     MOV R11D, 536871408        ; GENERIC-+
;      B09:       41FFD3           CALL R11
;      B0C:       488BE5           MOV RSP, RBP
;      B0F:       F8               CLC
;      B10:       5D               POP RBP
;      B11:       C3               RET
NIL

With the special operator compiler hint:
the.lisp
(declaim (optimize (debug 0) (safety 0) (speed 3)))

(defun my-adder ()
  (+ 5 (the (integer 0 65535) (remote-function))))

*slime-repl sbcl*
CL-USER> (disassemble 'my-adder)
; disassembly for MY-ADDER
; Size: 41 bytes
; 0379FF5F:       488D5424F0       LEA RDX, [RSP-16]          ; no-arg-parsing entry point
;       64:       4883EC18         SUB RSP, 24
;       68:       488B05A9FFFFFF   MOV RAX, [RIP-87]          ; #<FDEFINITION object for REMOTE-FUNCTION>
;       6F:       31C9             XOR ECX, ECX
;       71:       48892A           MOV [RDX], RBP
;       74:       488BEA           MOV RBP, RDX
;       77:       FF5009           CALL QWORD PTR [RAX+9]
;       7A:       480F42E3         CMOVB RSP, RBX
;       7E:       4883C20A         ADD RDX, 10
;       82:       488BE5           MOV RSP, RBP
;       85:       F8               CLC
;       86:       5D               POP RBP
;       87:       C3               RET
NIL