? (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 the
LETform. Otherwise, we would have seen one more
NILis 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
NAMEslot defaults to
PLAYERslot defaults to
NIL, and the position and velocity slots all default to
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
#<. 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>-Ppredicate 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-NAMEstructure 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
? (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.
NILif you use a
? (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
EMPLOYEEalso apply to a
MANAGER, and a
MANAGERinstance is also an
EMPLOYEEinstance. Notice in the following example how the
...-NAMEaccessors for both
EMPLOYEEreference 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
:INCLUDEoption, 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.