Lists

<-Previous | ^UP^ | Next->

Lists are fundamental to Lisp - LISt Processing

A list is defined textually as zero or more elements enclosed by parentheses. The elements can actually be anything, e.g. numbers, strings, objects, more lists, keywords

nil is defined an an empty list. It also acts as the Boolean value for false. This is one of the few places in Lisp where a single value assumes more than one meaning

Creating Lists

There are 2 basic ways to create a list

  • Using the list function

GDL-USER> (setq a (list 1 2 3))

(1 2 3)
  • Quoting a literal list

GDL-USER> (setq b '(4 5 6 7 8 9))

(4 5 6 7 8 9)
However, there are many other functions which return lists

Adding to lists

The Common Lisp function append takes 2 or more lists and returns a new list which results from appending them into a single list. It does not modify the given input lists. If the arguments are not lists then append will behave in different ways depending on where the non-list element is

  • If it is the last of the arguments append will generate a dotted list
  • If it is in any other position an error will be generated

GDL-USER> (append (list 1 2) (list 3 4))

(1 2 3 4)

GDL-USER> (append (list 1 2) (list 3 4) 5)

(1 2 3 4 . 5)

GDL-USER> (append 0 (list 1 2) (list 3 4))

; Evaluation aborted on #<TYPE-ERROR #x2103F1595D>.

GDL-USER> (append (list 1 2) 0 (list 3 4))

; Evaluation aborted on #<TYPE-ERROR #x2103EBBCBD>.

If the second argument to the Common Lisp function cons is a list, then cons will return a list with the first argument prepended to the front of it. Contrast the difference in output compared to append when the first argument to cons is a list

GDL-USER> (cons 1 (list 2 3 4))

(1 2 3 4)

GDL-USER>  (cons (list 1 2) (list 2 3 4))

((1 2) 2 3 4)

the Common Lisp macro push works in much the same way as cons but push modifies the list (its second argument) in-place, while cons simply returns a new list and does not modify its arguments in any way.

GDL-USER> (setq a nil)

NIL

GDL-USER> (push 1 a)

(1)

GDL-USER> (push 2 a)

(2 1)

GDL-USER> (push 2 a)

(3 2 1)

GDL-USER> a

(3 2 1)

GDL-USER> (push (list 1 2 3) a)

((1 2 3) 3 2 1)

Accessing elements within a list

The Lisp function first returns the first element of a list, whilst the Lisp function rest returns a list minus the first element

GDL-USER> (first a)

1

GDL-USER> (rest a)

(2 3)

Lisp also defines some archaically-named list access functions which you may encounter in legacy code,such as car (synonym for first), cdr (synonym for rest), and some compound ones such as cadr, caddr, and so on.

GDL-USER> (car a)

1

GDL-USER> (cdr a)

(2 3)

GDL-USER> (caddr a)

3

If you encounter any of these archaic names, you may look them up in a standard CL reference.

Common Lisp defines first through to tenth as functions to retrieve the corresponding element of a list

GDL-USER> (first a)

1

GDL-USER> (fourth b)

7

The Common Lisp function last returns the last element of a list as a list. To get the last element, use the GendL function lastcar

GDL-USER> (last a)

(3)

GDL-USER> (lastcar a)

3

Finally to return any element in a list, Common Lisp provides the function nth. nth takes an index number and a list and will return the the element at the position in the list defined by the index number. Note that nth is zero-based, so the first element in a list in (nth 0 [list]). There is a corresponding function nthcdr, again taking an index number and list as arguments which will return the nth cdr of a list

GDL-USER> (nth 3 b)

7

GDL-USER> (nthcdr 3 b)

(7 8 9)

Plists

Plists are a special type of list which is made up of keyword-value pairs. Rather than accessing elements of the list by position, the value element is accessed by referencing the keyword. Plists are a very convenient way to hold data and are widely used. The getf function, which takes a Plist and keyword as arguments returns the value immediately following the keyword

GDL-USER> (setq c (list :a 1 :b 2 :c 3))

(:A 1 :B 2 :C 3)

GDL-USER> (getf c :b)

2

Note that if a keyword is defined more than once in a plist, getf will return the value associated with the first occurrence of the keyword

GDL-USER> (setq c (list :a 1 :b 2 :c 3 :a 4))

(:A 1 :B 2 :C 3 :A 4)

GDL-USER> (getf c :a)

1

Values in a plist may be updated by using a combination of the Common Lisp setf and getf functions

GDL-USER> (setq d (list :a 1 :b 2 :c 3))

(:A 1 :B 2 :C 3)

GDL-USER> (setf (getf d :b) 10)

10

GDL-USER> d

(:A 1 :B 10 :C 3)

Using lists in GendL

The example below is based on assembly-5 from the Sequences of Objects tutorial, converted to use lists. A few features to observe

  • The number of my-box objects is now calculated based on the elements in the list defined by the :computed-slot box-lengths, using the Common Lisp function length
  • The length of each my-box object uses (the-child index) to access the nth element of the box-lengths list
  • The :computed-slot box-volumes makes use of the GendL macro list-elements to list all of the my-box objects and then send the volume message to them using the GendL macro the-element

(define-object assembly-8 (base-object):computed-slots((box-lengths (list 2 5 8))(box-centers (list (make-point 0 0 0)(make-point 6 0 0)(make-point 12 0 0)))(number-of-boxes (length (the box-lengths)))(box-volumes (list-elements (the my-box) (the-element volume)))(box-1-volume (nth 0 (the box-volumes)))):objects((my-box :type 'box:sequence (:size (the number-of-boxes)):length (nth (the-child index) (the box-lengths)):width 2:height 1:center (nth (the-child index) (the box-centers)))))

GDL-USER> (setq self (make-object 'assembly-8))

#<GDL-USER::ASSEMBLY-8 #x210456C58D>

GDL-USER> (list-elements (the my-box))

(#<BOX #x210456A86D> #<BOX #x210456F2BD> #<BOX #x210456EE9D>)

GDL-USER> (the box-volumes)

(4 10 16)