* Notes on Part II of LISP 3rd edition, Winston & Horn

Table of Contents

1 Chapter 11 Properties and Arrays

Examines the creation and use of property lists and arrays.

1.1 Properties

A symbol may have associated with it any number of symbolically indexed values, or properties

  • get is used to retrieve a property
  • setf is used with get to set a property
  • remprop is used to remove a property

1.1.1 get

To retrieve a property from a sybol:

(get <symbol> <property name>)

1.1.2 setf and get

In order to set a proptery on a symbol:

(setf (get <symbol> <property name>) <property value>)

1.1.3 remprop

In order to remove a property, you can set it to nil, but it is better to use remprop

(remprop <symbol> <property name>)

1.1.4 Examples

CL-USER> (setf (get 'bob 'father) 'bill)
BILL
CL-USER> (get 'bob 'father)
BILL
CL-USER> (setf (get 'bob 'wives) '(maria sally))
(MARIA SALLY)
CL-USER> (get 'bob 'wives)
(MARIA SALLY)
CL-USER> (remprop 'bob 'wives)
(WIVES (MARIA SALLY) FATHER BILL)
CL-USER> (get 'bob 'wives)
NIL    

1.1.5 Problems

  • 11-1
    (defun grandfather (son)
       (get (get son 'father) 'father)
    
    
  • 11-2
    (defun adam (descendant)
       (if (null (get descendant 'father)) 
                 descendant
                 (adam (get descendant 'father))))
    
    

1.2 Arrays

Arrays might be considered numerically indexed properties. The associated primitive functions are:

  • make-array (with setf)
  • aref (with setf)
  • array-dimension

1.2.1 make-array

You construct an array using make-array, and the argument is a list of sizes for each array dimension:

(make-array '(<dimension size 1> ...<dimension size n>))

A simple 1-dimensional array:

(make-array '(4))

A two-dimensional 4x6 array:

(make-array '(4 6))

Use setf to save the array to a symbol

(setf (make-array '(4)))
(setf (make-array '(4 6)))

  • initialization
    Use the :initial-element keyword parameter to initialize all elements in the array
    CL-USER> (make-array 3 :initial-element 'bork)
    #(BORK BORK BORK)
    
    CL-USER> (make-array '(3 2) :initial-element 'V)
    #2A((V V) (V V) (V V))
    

    Use the :initial-contents keyword parameter to arbitrarily set the values in the array, e.g.

    CL-USER> (setf bork-tense (make-array '(3 2) :initial-contents
                        '((bork V)
                          (borking VI)
                          (borked VP))))
    #2A((BORK V) (BORKING VI) (BORKED VP))
    

1.2.2 aref

Use the primitive function aref to get the value at a specified index:

CL-USER> (aref bork-tense 2 0)
BORKED

Use it with setf to set the value at that index

CL-USER> (setf (aref bork-tense 2 0) 'BORKED-UP)
BORKED-UP

1.2.3 array-dimension

The primitive function array-dimension returns the specified dimension:

CL-USER> (array-dimension bork-tense 0)
3
CL-USER> (array-dimension bork-tense 1)
2

1.2.4 array-dimensions

Reports the dimensions of an array

CL-USER> (array-dimensions (make-array '(3 2)))
(3 2)
CL-USER> (array-dimensions (make-array 4))
(4)

1.2.5 length

The primitive length works on sequences, and will work only on 1-dimensional arrays

CL-USER> (length (make-array 5))
5
CL-USER> (length (make-array '(5 5)))
; Evaluation aborted

1.2.6 array iteration

Use the macro dotimes in conjunction with array-dimension or array-dimensions to iterate over a list:

CL-USER> (defun iterate2d (2darray)
          (dotimes (i (car (array-dimensions 2darray)))
            (dotimes (j (cadr (array-dimensions 2darray)))
              (format t "~a " (aref 2darray i j)))))
ITERATE2D
CL-USER> (iterate2d bork-tense)
BORK V BORKING VI BORKED-UP VP 
NIL

2 Chapter 12 Macros and Backquote

Macros are useful for delaying evaluation and templating expressions. Covered in this chapter are:

One reason for defining a macro is a scenario in which you do not want one or more parameters evaluated initially (or only conditionally) especially when one or more parameter will induce a side-effect, for instance, in the following example, the action paramter is always evaluated, not just when the val arg is positive:

CL-USER> (defun when-positive (val action)
          (when (plusp val) action))

CL-USER> (when-positive -1 (print 'alarm))

ALARM 
NIL
CL-USER> (when-positive 1 (print 'alarm))

ALARM 
ALARM

2.1 defmacro

The macro definition:

(defmacro <name> (<parameters>) 
  (<form 1>...<form n>))

A macro does not evaluate is arguments; the body of the macro produces a form, which is then evaluated.

(defmacro when-positive-macro (val action)
   (list 'when (list 'plusp val) action))

When this macro is invoked, a list is produced (when (plusp val) action) which is then evaled. This has the desired effect of not invoking the action arg unless the val is positive:

CL-USER> (when-positive-macro 1 (print 'alarm))

ALARM 
ALARM
CL-USER> (when-positive-macro 0 (print 'alarm))
NIL

2.1.1 alternate example with eval

Of course, something similar could have been achieved in this case by:

(defun when-positive-eval (val action)
   (when (plusp val) (eval action)))

Which executes like:

CL-USER> (when-positive-eval 1 '(print 'action))

ACTION 
ACTION
CL-USER> (when-positive-eval -1 '(print 'action))
NIL

2.2 The quote ', backquote `, and the comma ,

The quote simply prevents evaluation of an expression; the backquote prevents the evaluation of an expression, but evaluates subexpressions preceeded with a comma (,):

CL-USER> (setf a-variable 'test)
TEST
CL-USER> a-variable
TEST
CL-USER> `(this is a ,a-variable)
(THIS IS A TEST)

Our macro can now be re-written much more cleanly:

(defmacro when-positive-macro (val msg)
   `(when (plusp ,val) (print ,msg)))

2.3 The difference between "," and ",@"

The comma operator can be thought of as an insert, whereas the ",@" can be thought of as a splice:

CL-USER> (setf variable '(different example))
(DIFFERENT EXAMPLE)
CL-USER> `(this is a ,variable)
(THIS IS A (DIFFERENT EXAMPLE))
CL-USER> `(this is a ,@variable)
(THIS IS A DIFFERENT EXAMPLE)
CL-USER> 

2.4 Style

First, one should document macros with an example of the form to which you expect the macro to expand.

Secondly, it is good practice to place all macros in a larger program in a separate file. The reason for this is that you should compile the macros first since compilers assume any forward references to procedures are functions, not macros; placing macros in a separate file makes it easier to ensure that the macros get compiled first.

When debugging a macro, you can pretty-print the expansion, like so:

CL-USER> (defmacro append-macro (l &rest args)
          (pprint `(append ,l ',args)))
APPEND-MACRO
CL-USER> (append-macro '(a b) a b)

(APPEND '(A B) '(A B))
NIL

2.5 Problems

2.5.1 12-1

(defmacro put (symbol value property-name)
   `(setf (get ,symbol ,property-name) ,value))

2.5.2 12-2

(defmacro getq (symbol property)
   `(get ',symbol ',property))

(defmacro putq (symbol property value)
   `(setf (get ',symbol ',property) ,value))

2.5.3 12-3

(defmacro when-nil (trigger result)
   `(when (not ,trigger) ,result))

2.5.4 12-4

(defmacro letq (bindings &rest body)
   `(let ,(mapcar #'(lambda(k-v-pair)
                   (list (car k-v-pair) (cons 'quote (cdr k-v-pair))))
                   bindings)
      ,@body))

2.5.5 12-5


2.5.6 12-6

(defun punctuate (list-arg &rest args)
           (append list-arg args))

2.5.7 12-7

(defmacro punctuate-macro (list-arg &rest args)
   `(append ,list-arg ',args))

Notes that the second line is not

   `(append ,list-arg ,args))
as you might expect; normally in a function, the &rest params are gathered up in a list and bound to args when the parameters are evaluated; however in a macro, the &rest parameters must be gathered up in a list like (a b c) so that we must actually quote the list in the macro when performing the append statement.

This would explain why the following also works

(defmacro punctuate (list-arg &rest args)
   `(append ,list-arg (list ,@args)))

Author: <grantham@Io.jupiter.net>

Date: 2008/07/08 00:51:08

HTML generated by org-mode 6.05a in emacs 23