Declarative Languages
Lecture #8

Purpose: Equality, hash-tables and blocks.

8.1 Introduction: equality

We encountered earlier various predicates for comparing specific types of lisp object:

and one predicate for comparing general lisp objects. I have assured you that if two objects print the same then they are equal. Now let's get closer to the truth, by introducing two further general equality predicates (each taking precisely two arguments): the functions eq and eql.

8.2 Equality and eq

Two objects are eq only if they are in fact the same object. Quite how this works depends on their types. Obviously, if the objects have different types they cannot be the same object and so they cannot be eq.

If you have two pointers to the same thing then they will be eq. For example, (eq something something) is true no matter what value something has. The following function too will always return true (for any argument):
(defun always-true (thing)
  (let* ((my-list (list thing)))
    (eq thing (first my-list))))
Note that if a function generates new objects, then these cannot be eq to each other:
CL-USER 21 > (let* ((things nil))
               (dotimes (i 2)
                 (push '(t) things))    ; pushing the same object each time
               (eq (first things)
                   (second things)))
T

CL-USER 22 > (let* ((things nil))
               (dotimes (i 2)           ; pushing s new object each time
                 (push (list t) things))
               (eq (first things)
                   (second things)))
NIL

CL-USER 23 >

This includes all functions (e.g. copy-list) which are defined as returning a fresh copy of some object, for example:

    (let* ((foo '(1 2 3 4))) (equal foo (butlast foo 0)))  =>  t
    (let* ((foo '(1 2 3 4))) (eq foo (butlast foo 0)))     =>  nil

8.3 Equality and eql

Two objects are eql if

A large number of lisp functions use a predicate for comparing objects; this tends to be specified as an optional argument and the default value is typically eql (see section 17.2.1 of the HyperSpec). As an example, consider the function position which takes an object and a sequence, and returns the first index into the sequence at which the object was found (or nil if it was not found):
(position 'wibble '(foo bar wibble baz wombat))  =>  2
The objects are compared by eql, unless another predicate is handed in as the value to the :test argument
(position "wibble" '("foo" "bar" "wibble" "baz" "wombat"))
        =>  nil
(position "wibble" '("foo" "bar" "wibble" "baz" "wombat") :test 'string=)
        =>  2
Related to position is the function position-if which takes a predicate (of one argument) and a sequence:
(position-if (lambda (x) (and (numberp x) (plusp x) (evenp x)))
             '(digits of pi are 3 1 4 1 5 9))
        =>  6
and related to both of these are find and find-if, which return the item which was found rather than its position.
(let ((bits '("foo" "bar" "wibble" "baz" "wombat")))
  (eq (third bits)
      "wibble"))  =>  nil

(let ((bits '("foo" "bar" "wibble" "baz" "wombat")))
  (eq (third bits)
      (find "wibble" bits :test 'string=)))  =>  t

8.4 Revisiting equal

Two objects are equal if

8.5 Hash-tables (an excuse for knowing about eql)

We know about the following general (in the sense that they can contain any lisp objects) data structures:

We now introduce the hash-table.  This is a data structure whose indices may be general lisp objects, which offers flexibility similar to lists and which delivers lookup times intermediate between lists and vectors.
 
Name Index by Flexibility Data ordered into sequence? Speed Use
cons first and rest good yes slow access over long sequence building lists and binary trees
vector numerical index poor yes fast, independent of length of sequence random lookup and rapid traversal of large data sets
structure field name (not available at run-time) poor no fast, independent of number of fields user-defined types
hash-table any lisp object good no intermediate dictionaries, general object maps

If we weren't bothered about lookup times, we could implement something like this with lists:

(and with more, slightly nastier code to add, reset and remove the phone numbers). Using hash-tables hides the above nastiness and is reasonably fast even when it gets large.

To make a hash-table, call the function make-hash-table. To look values up in the table use the function gethash (setfable). To remove a single entry altogether use remhash, and to empty a hash-table completely call clrhash. For example:

Notes: Once you've built a hash-table, a useful function for traversing it is maphash, which takes a function and hash-table as arguments. The function is invoked repeatedly for each entry in the table, with two arguments (a key and the corresponding value). For example: Note: 8.6 Blocks

We have met the macro return which allows "premature" exit from the various looping macros (dotimes dolist loop etc). A generalization of this is the special operator return-from which in particular allows early exit from any (named) function.
 

CL-USER 14 > (defun one-value (table)
               (maphash (lambda (key value)
                          (declare (ignore key))
                          (return-from one-value
                            value))
                        table))
ONE-VALUE

CL-USER 15 > (one-value *table*)
2330

CL-USER 16 >

The above (admittedly somewhat pointless) function returns one value extracted from the hash-table supplied as its argument. Also in the above code you should be aware of the following: 8.7 Practical session / Suggested activity

Convert last week's work to store student records in a hash-table (accessible by name) rather than in a list. Write functions to add a new student, to find the record of a student with a given name, and to delete a student.

As before, write functions to name the three students who have the highest marks, or to spot which lecturer fails most of their students.

Use return-from in a function to return the SID of any student who hasn't attmpted any modules at all.

Comment on which data stucture was "best". [Define "best".]

8.8  Further reading & exercises

Copyright (C) Nick Levine 1999. All rights reserved.
Last modified 2000-09-14
$Id: //info.ravenbrook.com/user/ndl/lisp/declarative/lectures/lectures/lecture-8.html#2 $