Lisp data types and data structures Class Materials for CCS109, Fall 1997 Lee Spector First, a digression on the FORMAT function. The FORMAT function produces formatted output to character streams -- it is very flexible and has many options. The first argument to FORMAT should be a stream. For now we will just use T for output to the listener. The second argument to FORMAT should be a string, which may or may not contain "directives". Here are some simple cases (no directives): (format t "Howdy there in TV land!") (format t "I had a little froggy.") The "tilde" character (~) is used to signal a directive. One of the simplest and most useful directives is ~%, which means to output a newline character: (format t "~%Bop bop~% shoo~% bob~% bop bop bam") Even ~% has more options -- for example, a number between the ~ and the % will cause that many newlines to be printed: (format t "jump down ~5% turn around.") The second handiest format directive is ~A. This means "go get the next lisp object from the argument list and print it here." (format t "~%this is ~a, so ~a ~a ~a!" 'swell 'stomp 'and 'yell) (format t "~%this is ~a, so ~a ~a ~a!" '(a list) 4 "ever" 'tell) There must be enough arguments in the call to format; extras will be ignored: (format t "~%this is ~a, so ~a ~a ~a!" '(a list) 4 "ever") (format t "~%this is ~a, so ~a ~a ~a!" '(a list) 4 "ever" 'tell 'somebody) Format directives can be used to print numbers in a wide range of formats (setq n 54321) (format t "Here's n:~A." n) (format t "Here's n:~O." n) (format t "Here's n:~X." n) (format t "Here's n:~E." n) (format t "Here's n:~B." n) (format t "Here's n:~5R." n) ;; base 5 (format t "This year:~@R." 1995) ;; roman (format t "Here's n:~R." n) ;; english (format t "Here's n:~10D." n) (format t "Here's PI:~F." PI) (format t "Here's PI:~15,5F." PI) (format t "Here's PI:~15,5,,,'*F." PI) (format t "Here's PI as money:$~$." PI) See CLTL2 for many more variations. The power of FORMAT goes well beyond numeric formatting. Consider this example from Wilensky: (setq n 1) (format t "~D boy~:P left" n) (setq n 2) (format t "~D boy~:P left" n) The ~{ and ~} directives can be used to perform iteration within a format string: (setq folks '(candy sandy wendy henry ben)) (format t "It was a mess. ~{~%~A fell down,~}OUCH!" folks) There are format directives for case-conversion, tables, indirection, etc... See CLTL2 for details. Providing NIL as the first argument to FORMAT causes the string to be RETURNED rather than printed. (progn (setq my-string (format nil "wowie zowie!")) 'hmmm) my-string The common lisp type hierarchy Common Lisp has lots of built-in types, see CLtL2 p.50 (type-of 'x) (type-of '(x y z)) (type-of 99) (type-of 3.141592) (type-of (make-array '(10 10))) (setq my-array (make-array '(10 10))) (type-of (aref my-array 5 5)) (setf (aref my-array 5 5) 99) (type-of (aref my-array 5 5)) (setq my-num 99) (type-of my-num) (typep my-num 'fixnum) (typep my-num 'integer) (typep my-num 'number) (typep my-num 'float) (typep my-num 'bignum) (typep 128972198732987326428714876328763 'bignum) DEFSTRUCT name-and-options [doc-string] {slot-description}* [Macro] defines a new structure, according to name-and-options, with slots described by the slot-descriptions. You COULD build complex data structures out of lists: I'll represent a PERSON as a list of (name age shoe-size parents children) where: name is a list of atoms age is an integer shoe-size is an integer parents is a list of names children is a list of names For example: (setq person-1 '((betty byte) 22 4 ((boopsie byte)(barney byte)) ((bobby byte)(belinda byte)))) We could then write some functions to access elements of a PERSON: (defun person-name (p) (first p)) (defun person-age (p) (second p)) (defun person-shoe-size (p) (third p)) (defun person-parents (p) (fourth p)) (defun person-children (p) (fifth p)) (person-name person-1) (person-shoe-size person-1) (person-children person-1) We could also write a function with keyword arguments to make the construction of new PERSONs easier: (defun make-person (&key (name '(ordinary jo)) (age 20) (shoe-size 5) (parents nil) (children nil)) "Returns a PERSON with the given attributes" (list name age shoe-size parents children)) (make-person :name '(consina cadarowitz)) (make-person :name '(consina cadarowitz) :age 15 :parents '((candy car)(charlie cdr))) We could also write functions to DESTRUCTIVELY modify PERSONs. This requires the use of SETF: (defun set-person-name (p new-name) (setf (car p) new-name)) (set-person-name person-1 '(boxcar willie)) person-1 DEFSTRUCT makes life much easier: (defstruct person (name '(ordinary jo)) (age 20) (shoe-size 5) (parents nil) (children nil)) This AUTOMATICALLY does the following: € Creates a new data type called PERSON, which is a STRUCTURE with 5 SLOTS. € Defines 5 new functions PERSON-NAME, PERSON-AGE, PERSON-SHOE-SIZE, PERSON-PARENTS, and PERSON-CHILDREN, each of which takes a PERSON as its argument and returns the associated slot of that PERSON. € Defines a predicate PERSON-P of one argument that is true if its argument is a PERSON. € Defines a function MAKE-PERSON with 5 keyword arguments corresponding to the slots of PERSONs. Note: € Structures are not lists. € Structures may be destructively modified using SETF and the automatically defined access functions. € DEFSTRUCT has many more features that we'll ignore for now. (setq person-2 (make-person :name '(sally silicon))) person-2 (person-name person-2) (person-shoe-size person-2) (person-p person-2) (person-p nil) (person-p person-1) person-2 (setf (person-children person-2) '((sam silicon))) person-2