NIL
.
? (defstruct foo-struct a b c) FOO-STRUCT ? (let ((foo-1 (make-foo-struct :a 1 :b "two"))) (print (foo-struct-b foo-1)) (print (foo-struct-c foo-1)) (values)) "two" NIL
NOTE: We use theIn cases where(values)
form to suppress the return value from theLET
form. Otherwise, we would have seen one moreNIL
printed.
NIL
is a reasonable default value, this behavior is
acceptable. But if the normal value of a slot is numeric, for example, you'd
really like to start with a reasonable default value rather than having to
add a test in all of the code which uses a structure. The full form of a slot
specification is a list of the slot name, its default value, and additional
options; specifying a bare name instead of the complete list is shorthand for
"default value of NIL, and no options."
? (defstruct ship (name "unnamed") player (x-pos 0.0) (y-pos 0.0) (x-vel 0.0) (y-vel 0.0)) SHIPWhen we instantiate this structure using
(MAKE-SHIP)
, the NAME
slot defaults to "unnamed"
, the PLAYER
slot defaults to
NIL
, and the position and velocity slots all default to 0.0
.Of course, we can still specify slot values to override the defaults:
? (make-ship :name "Excalibur" :player "Dave" :x-pos 100.0 :y-pos 221.0) #S(SHIP :NAME "Excalibur" :PLAYER "Dave" :X-POS 100.0 :Y-POS 221.0 :X-VEL 0.0 :Y-VEL 0.0)Lisp's default printer for structures makes it easy to see the slots and their values. We've given explicit values to all of the slots except the two velocity slots, which have their default values.
? (defstruct (ship (:print-function (lambda (struct stream depth) (declare (ignore depth)) (format stream "[ship ~A of ~A at (~D, ~D) moving (~D, ~D)]" (ship-name struct) (ship-player struct) (ship-x-pos struct) (ship-y-pos struct) (ship-x-vel struct) (ship-y-vel struct))))) (name "unnamed") player (x-pos 0.0) (y-pos 0.0) (x-vel 0.0) (y-vel 0.0)) SHIP ? (make-ship :name "Proud Mary" :player 'CCR) [ship Proud Mary of CCR at (0.0, 0.0) moving (0.0, 0.0)]Actually, it's considered bad practice to print something the reader can't interpret. Our use of the brackets around the printed ship description is not necessarily good or bad, it depends upon how the current read table is specified (we first saw reader macros in Chapter 3, Lesson 12.
One way to ensure that the reader doesn't get confused is to deliberately print
something so as to be unreadable. By convention, Lisp prints such objects
beginning with #<
. You could change your format string to read
"#<ship ~A of ~A at (~D, ~D) moving (~D, ~D)>"
, so the prior
MAKE-SHIP
example would print #<ship Proud Mary of CCR at (0.0,
0.0) moving (0.0, 0.0)>
. However, since 1990 Lisp systems have had a
PRINT-UNREADABLE-OBJECT
macro which should be used for this
purpose. If the printer control variable *PRINT-READABLY*
is true,
PRINT-UNREADABLE-OBJECT
will signal an error.
;; Use PRINT-UNREADABLE-OBJECT macro -- changes in boldface ? (defstruct (ship (:print-function (lambda (struct stream depth) (declare (ignore depth)) (print-unreadable-object (struct stream) (format stream "ship ~A of ~A at (~D, ~D) moving (~D, ~D)" (ship-name struct) (ship-player struct) (ship-x-pos struct) (ship-y-pos struct) (ship-x-vel struct) (ship-y-vel struct)))))) (name "unnamed") player (x-pos 0.0) (y-pos 0.0) (x-vel 0.0) (y-vel 0.0)) SHIP
? (defstruct (bar (:type vector)) a b c) BAR ? (make-bar) #(NIL NIL NIL)Note that the slot names are not stored when you specify the storage type. This is probably the biggest advantage for using this option -- it can save storage in the amount of a machine word per slot per instance. The disadvantage is that Lisp does not recognize such a structure as a distinct type, and does not create a
<structure-name>-P
predicate for you.If you are satisfied with being able to retrieve the name of the structure, but still want the storage savings associated with specifying the structure's representation, you can do this:
? (defstruct (bar (:type vector) :named) a b c) BAR ? (make-bar) #(BAR NIL NIL NIL)Using the list representation option has the drawbacks noted above, but none of the advantages; the backbone of the list typically adds a machine word of storage per slot when compared to the default representation, which is usually a vector. The only time it would make sense to explicitly specify a list representation is when the default structure representation is list-based or when the Lisp implementation imposes some artificial limit on the space reserved for storage of vectors; neither case applies in modern implementations.
:CONC-NAME
structure option to specify a name to use instead
of the structure name.
? (defstruct (galaxy-class-cruiser-ship (:conc-name gcc-ship-)) ; name includes trailing hyphen! name player (x-pos 0.0) (y-pos 0.0) (x-vel 0.0) (y-vel 0.0)) GALAXY-CLASS-CRUISER-SHIP ? (let ((ship (make-galaxy-class-cruiser-ship))) (print (gcc-ship-x-pos ship)) ; note abbreviated accessor name (values)) 0.0
:CONSTRUCTOR
option.
? (defstruct (3d-point (:constructor create-3d-point (x y z))) x y z) 3D-POINT ? (create-3d-point 1 -2 3) #S(3D-POINT :X 1 :Y -2 :Z 3)
NOTE: The slot values do not default toMost lambda-list options are available to the constructor function -- consult a Lisp reference manual for details.NIL
if you use a:CONSTRUCTOR
option!
:INCLUDE
option.
? (defstruct employee name department salary social-security-number telephone) EMPLOYEE ? (make-employee) #S(EMPLOYEE :NAME NIL :DEPARTMENT NIL :SALARY NIL :SOCIAL-SECURITY-NUMBER NIL :TELEPHONE NIL) ? (defstruct (manager (:include employee)) bonus direct-reports) MANAGER ? (make-manager) #S(MANAGER :NAME NIL :DEPARTMENT NIL :SALARY NIL :SOCIAL-SECURITY-NUMBER NIL :TELEPHONE NIL :BONUS NIL :DIRECT-REPORTS NIL)All accessors which apply to an
EMPLOYEE
also apply to a
MANAGER
, and a MANAGER
instance is also an
EMPLOYEE
instance. Notice in the following example how the
...-NAME
accessors for both MANAGER
and
EMPLOYEE
reference the same slot.
? (setq mgr (make-manager)) #S(MANAGER :NAME NIL :DEPARTMENT NIL :SALARY NIL :SOCIAL-SECURITY-NUMBER NIL :TELEPHONE NIL :BONUS NIL :DIRECT-REPORTS NIL) ? (setf (manager-name mgr) "Buzz") "Buzz" ? (employee-name mgr) "Buzz"A structure may have one
:INCLUDE
option, at most. This limits the
ability of structures to model the real world by describing inheritance. CLOS
objects allow multiple inheritance, and have many other useful and convenient features.
We will get our first look at CLOS in Chapter 7.