CS 2360 - October 6, 1998

Lecture 4 -- Program Control


The substitution model of evaluation

I didn't go any detail last week about how LISP evaluates a function
call, so let's take a moment to do that now.  We won't go into all 
the details here; we'll leave some of the details for the weeks to 
come.  But this little discussion should give you enough understanding 
to make you dangerous.

Let's say you've created a file with all the LISP code for evaluating the 
quadratic formula that I gave you last week.  Then let's say that you've 
also added your implementation of the function for computing the square 
root of the discriminant, as required by the first homework assignment.
Now you tell the LISP interpreter to evaluate all those defun functions, 
as you were shown in your lab session.  You're ready to compute stuff.  
If you type (pos-root 1 0 -4) at the LISP evaluator, here's what will happen:

? (pos-root 1 0 -4)
2
? 

You can follow that a little bit more closely by telling the evaluator that 
you want to "trace" some of the functions that are being called:

? (trace pos-root pos-numerator sqrt-of-discriminant denominator)
NIL
?

Then when you type (pos-root 1 0 -4), you'll see something like this:

? (pos-root 1 0 -4)
 Calling (POS-ROOT 1 0 -4) 
  Calling (POS-NUMERATOR 1 0 -4) 
   Calling (SQRT-OF-DISCRIMINANT 1 0 -4) 
   SQRT-OF-DISCRIMINANT returned 4
  POS-NUMERATOR returned 4
  Calling (DENOMINATOR 1) 
  DENOMINATOR returned 2
 POS-ROOT returned 2
2

What's really happening is something like this:  When I type the list 
(pos-root 1 0 -4) at the interpreter, LISP assumes I want to invoke the 
function "pos-root".  LISP looks up the definition of "pos-root" and 
extracts that definition.  It then evaluates the arguments, 1, 0, and 
-4, which in this case conveniently evaluate to themselves. Then, 
LISP applies that definition to the evaluated arguments.  This can be 
viewed as a substitution, where:

(pos-root 1 0 -4)

  becomes

(/ (pos-numerator 1 0 -4) (denominator 1 0 -4))

This expression is sent to the evaluator again.  LISP evaluates the arguments, 
but this time the arguments aren't numbers but are instead lists.  So these 
expressions are handed to the evaluator, and LISP treats them as function 
calls.  

The order in which these function calls, or any arguments in this example, 
are evaluated won't be important.  Why?  Because of the functional programming 
constraints we've followed, there are no side-effects.  For example, computing 
pos-numerator in no way affects the computation of denominator and vice-versa, 
so it doesn't matter which one is computed first.  (This will be a very nice 
feature in the parallel-processing world of the future.)  If, on the other 
hand, one of these functions updated some global variable that was accessed 
by the other, we would most definitely worry about which one was computed 
first, resulting in a harder programming job, more complex software, and so on.

Still, something like Macintosh Common LISP (a very bulletproof implementation
and my personal favorite) has to evaluate one of these arguments before the 
other, and the way it works (as do most LISP systems) is to evaluate the 
arguments left to right.  So LISP evaluates the expression 
(pos-numerator 1 0 -4) and does the appropriate substitution:

(/ (pos-numerator 1 0 -4) (denominator 1 0 -4))

  becomes

(/ (+ (- 0) (sqrt-of-discriminant 1 0 -4)) (denominator 1 0 -4))

This "substitution" process continues until LISP arrives at a solution 
(which requires that all the functions you defined are eventually replaced by 
functions that were already defined in LISP).  In short, the LISP function 
evaluation process can be described like this:

1.  Look up and retrieve the function definition.
2.  Evaluate the arguments to the function (by passing the
    arguments themselves to the LISP evaluation function---
    numbers evaluate to themselves, symbols evaluate to 
    the objects they're bound to, while lists are treated
    as function calls and evaluated accordingly).
3.  Apply the function definition to the evaluated arguments
    (i.e., replace the argument place-holders in the new
    function definition with the corresponding evaluated
    arguments).
4.  Substitute the result of all that for the original
    function call from step 1.
5.  Pass this new expression to the evaluator.

Warning:  The is a very sketchy and hand-wavy description of the evaluation 
process.  It should suffice for now, but as time goes by, we'll beef this 
description up with details about how various types of things get evaluated.  
But rather than dump all the details on you at once, we'll add things 
incrementally as we need them.


The conditional

A programming language really isn't worth much if there's no 
way to change the flow of program control.  Being able to 
take different branches depending on the results of some test 
is what makes computer programs useful.  Without conditionals,
computing in general would be really boring.  The basic mechanism 
for doing this is called the "conditional", and in LISP the 
fundamental conditional is called "cond".  Here's the syntax:

(cond (*test1* *action1*)
      (*test2* *action2*)
          :
          :
      (*testN* *actionN*))

If the expression *test1* evaluates to non-nil, then the 
"cond" function returns what the expression *action1* 
evaluates to.  (Since *test1* is an expression, we'd expect 
to see a function call there, or maybe a symbol bound to some 
value...that sort of thing.)  If *test1* evaluates to nil, 
the "cond" skips to *test2*, which is evaluated as above, 
and so on.  Each test-action pair is called a "cond clause".

If all the tests are evaluated in sequence, and all tests 
evaluate to nil, then the "cond" returns nil.  While you can 
count on this to happen, it may not be immediately obvious to 
other folks who read the "cond" expression exactly what the 
original programmer intended to occur in this case.  Good 
programming style in general demands that you make your 
intentions explicit in your code.  Here, that means you 
should always end your "cond" with a cond clause which makes 
it obvious what you expect to happen when all the previous 
tests evaluate to nil.  You do it like this:

(cond (*test1* *action1*)
      (*test2* *action2*)
          :
          :
      (*testN* *actionN*)
      (T *what you want to happen if all else fails*))

Also, you can have more than one action in each cond clause.  
If the test is non-nil, the associated actions will be 
evaluated left-to-right, and the last expression evaluated 
will be the one returned by the "cond" function.  (Note, 
though, that since we're not letting you create any side-
effects by assigning values to variables yet, this feature 
won't be all that useful to you just now.)  What kinds of 
tests already exist for you to use?  Here's a quick lesson:


Predicates

Common LISP provides a set of functions which are designed to 
execute useful tests and Boolean or true/false values 
depending on the outcome of the test.  These are called 
predicates, and we use them all the time as the tests in our 
"cond" functions.  Here are some commonly-used predicates:

  (null *expr*)      returns non-nil if *expr* is the empty
                     list, nil if *expr* is not empty

  (atom *expr*)      returns non-nil if *expr* is an atom,
                     nil if *expr* is not an atom

  (numberp *expr*)   returns non-nil if *expr* is a number,
                     nil if *expr* is not a number

  (listp *expr*)     returns non-nil if *expr* is a list,
                     nil if *expr* is not a list

  (symbolp *expr*)   returns non-nil if *expr* is a symbol,
                     nil if *expr* is not a symbol

Historically, many functions designed to work as predicates 
(i.e., returning true/false values) have had the letter "p" 
appended to their names, hence "numberp" and "listp".  
Obviously, folks haven't been too consistent in this, since 
"atom" is not "atomp".  It's quaint idiosyncrasies like this 
that give any language some personality, no?  Sometimes, this 
sort of stuff filters into everyday language use.  For 
example, one LISP hacker might ask if another is interested 
in going to lunch by saying simply "lunchp?"....I guess you 
had to be there.


Equality predicates

There are several equality predicates worth knowing about.  
In "A Programmer's Guide to Common LISP", Deborah G. Tatar 
explains it pretty well (pp. 48-50):

"...there are four important general tests for equality.  
These tests take any two LISP objects as arguments, and check 
to see if they are equal.  Naturally, two objects must be of 
the same type to be equal.

You might wonder why four tests are necessary.  Why doesn't 
one test serve the purpose?  The reason is that there are 
degrees of equality.  Most of the time you want to know 
whether two objects look the same, but sometimes you have to 
know whether they are actually the same object in memory.  
That accounts for two of the tests.  Then, as it turns out, 
minor modifications on each of the major tests make two more 
surprisingly useful functions.

EQUALP and EQUAL are the more general equality predicates.  A 
good rule of thumb is that two objects are EQUALP or EQUAL if 
they look the same when they are printed on the screen.

:
:

The difference between EQUAL and EQUALP is that EQUALP is 
less pure in its definition of equality.  Simply because it 
turns out to be useful, EQUALP ignores differences in case in 
characters and type in numbers.  For example,

(equal 3 3.0)
NIL

but

(equalp 3 3.0)
T

Also,

? (equal 3/4 0.75)
NIL
? (equalp 3/4 0.75)
T
?  ]

Furthermore,

(equal "YES" "yes")
NIL

(equalp "YES" "yes")
T

The last example demonstrates one of the instances in which 
EQUALP is useful; if you had solicited user input, you 
probably wouldn't care whether it was typed in lower-, or 
uppercase letters, or both.

The other two equality predicates, EQ and EQL, tell you 
whether you are looking at two objects in memory or at one.  
Why do we need operators like these?  Consider the following 
calls and returned values:

(equal (cons 'a 'b) (cons 'a 'b))
T

(equalp (cons 'a 'b) (cons 'a 'b))
T

These might look like good answers, and for many purposes 
they are; however, consider that CONS is a function that 
performs an operation.  Each time you call CONS, a new cons 
cell is constructed.  The contents of two cons cells may be 
the same or look the same but they are separate objects, just 
as twins who have DNA with the same sequence of nucleotides 
are still separate persons.  EQ and EQL test whether two 
objects not only look alike, but whether they are the same, 
that is, located in the same place in memory.  In other 
words,

(eq (cons 'a 'b) (cons 'a 'b))
NIL

(eql (cons 'a 'b) (cons 'a 'b))
NIL

This kind of test is important when you have the ability to 
change objects.  Then you often need to know whether both 
items will change, or only one.

:
:

One characteristic difference between EQ and EQL has to do 
with the way LISP handles numbers.  EQ returns true only if 
two numbers are in exactly the same location in memory.  
Small numbers (called FIXNUMS) have a direct representation 
in memory, and are always EQ.  However, LISP must create a 
representation for very large numbers (BIGNUMS) and for 
floating-point numbers each time they are used.  Therefore, 
they may not be EQ.  It turns out that much of the time you 
won't care about exact identity in that case.  Furthermore, 
the number of fixnums is implementation-dependent.  EQL is 
provided as a portable version of EQ.  For example, in a 
given implementation of LISP:

(eq 1234567890 1234567890)

may return T or NIL, but:

(eql 1234567890 1234567890)

always returns T.

The difference between EQ and EQL is rather subtle; in fact, 
the only reason for introducing EQL at this early stage is 
that it is the default test that LISP functions use to test 
for equality."

In addition, there's yet another useful equality predicate,
which is simply =.  The = predicate takes only numeric arguments;
anything else will cause an error.  It works on numbers of different
type, so that 

(= 4 4) returns T, and

(= 4 4.0) also returns T.


Using "cond" -- an example

Let's say we want to define a function which tells us if a 
given item is an element of a given list.  This turns out to 
be a very useful function, and it already exists in Common 
LISP.  It's called "member".  But even though it already 
exists, we want the practice, so we're going to construct our 
own version.  And to make sure we don't inadvertently replace 
LISP's version with our own possibly buggy version, we'll 
give ours a distinctive name.  Following a tradition handed
down through generations of programming courses, we'll use
the convention of creating these distinctive names by taking
the name of the LISP function we're trying to mimic and adding
the prefix "my-" to it.  Thus we generate the name "my-member"
for our own version of "member".

What will the design look like?  We can sketch it out with a 
combination of the LISP syntax we already know, and some 
English where we're not sure about the LISP yet.  Here's the 
first cut:

  (defun my-member (input-item input-list)
         if done then return "no"
    else if input-item = first element of input-list
         then return "yes"
    else what?  see if input-item = next thing on input-list?
         how? )

OK, so how are we going to turn all that "if-then-else" stuff into a "cond"?

  (defun my-member (input-item input-list)
    (cond (done then return "no")
          (input-item = first element of input-list
           then return "yes")
          (what?  see if input-item = next thing on 
           input-list? how? ) ) )

Hmmm.  That looks a little more like LISP, but it sure won't 
run on my Macintosh.  What looks like something that's going 
to be real easy to turn into LISP?  How about that test to 
see if input-item is the same as the first element of input-
list?  That should be easy.  Just remember the "cond" syntax:

  (defun my-member (input-item input-list)
    (cond (done then return "no")
          ((eql input-item (first input-list))
           then return "yes")
          (what?  see if input-item = next thing on 
           input-list? how? ) ) )

And how do we return "yes" in that case?

  (defun my-member (input-item input-list)
    (cond (done then return "no")
          ((eql input-item (first input-list)) T)
          (what?  see if input-item = next thing on 
           input-list? how? ) ) )

Nothing to it.  How are we going to test if we're done?  
Well, if we just sort of walk along input-list, testing the 
individual elements to see if they match input-item, what 
would be the termination point?  When we run out of input-
list, or, in other words, when input-list is nil.  So now we 
can translate more English into LISP:

  (defun my-member (input-item input-list)
    (cond ((null input-list) nil)
          ((eql input-item (first input-list)) T)
          (what?  see if input-item = next thing on 
           input-list? how? ) ) )

Wow.  Now I have more LISP than English.  But there's still 
one missing chunk.  How do I get this thing to repeat for 
every element of input-list (or at least until I match input-
item)?  If we were piddling around with Pascal, we'd want to 
create some sort of loop structure, and maybe create a 
variable or two, and throw in an assignment operation here 
and there...make it really complicated, and in the process 
make ourselves feel good about how much mastery we have over 
our computer.  Grrrrr.

Well, that's not gonna happen here.  Not today at least.  
We're going to use a very elegant and computationally pure 
form of iteration which LISP supports very nicely.  It's 
called recursion.


Recursion

"Recursion" essentially means defining something in terms of 
itself.  A function is recursive if it (directly or 
indirectly) calls itself.  A recursive function consists of 
three parts:

1)  the termination condition, or when to stop
2)  the operation or modification, or what to do to the input
    to move closer to a termination condition
3)  the recursive call itself.

Recursion is a program control mechanism that allows 
repetitive operations without traditional iteration, which 
requires the use of side effects and the maintenance of 
variables as counters or temporary storage places...things 
which add unnecessary complexity.  Using recursion 
effectively requires a different style of thinking, but 
you'll get better at it with practice if you find it 
difficult early on.  Recursion also results in nice, clean, 
compact source code which is often easier to read than the 
iterative equivalents.  A recursive function can also eat up 
lots of memory as it is running, but it doesn't necessarily
have to; we'll see more about this later.

Let's go back now and finish "my-member".  What do we want to 
do?  With "my-member", we're trying to build a function which 
does some operation on all the elements of a list, until we 
find a specific element.  If we're thinking recursively, we 
want to break this up into a couple of smaller problems 
(there's that abstraction thing again):

1)  performing that operation of one element of the list,
    combined somehow with...

2)  calling the function just defined on the remainder of the
    list

So let's apply all this thinking about recursion to "my-
member".  So far, we've already coded two different 
termination conditions: stopping when we get to the end of 
input-list without finding a match, and stopping when we find 
a match with input-item.  And the test to see if we find a 
match between input-item and the first element of input-list 
is effectively the "performing that operation of one element 
of the list" that we just mentioned.  But if neither of those 
conditions is true, what do we want to do?  We want to call 
"my-member" on the remainder of input-list, since that will 
get our matching operation performed on the next element of 
the list, while at the same time reducing the size of input-
list and thereby getting us closer to a termination 
condition.  The end result looks like this:

  (defun my-member (input-item input-list)
    (cond ((null input-list) nil)
          ((eql input-item (first input-list)) T)
          (T (my-member input-item (rest input-list)))))

Oh, one other thing.  When "my-member" finds a match, it 
returns T.  But when Common LISP's "member" function returns 
a match, it returns that part of input-list which begins with 
input-item.  That's also a non-nil result, so it has the same 
Boolean value, but it gives us more information than just 
"true" or "false".  You'll find that LISP tries to do that a 
lot, and you should think about doing it too when you can.  
To make "my-member" work that way, it would be changed to 
this:

  (defun my-member (input-item input-list)
    (cond ((null input-list) nil)
          ((eql input-item (first input-list)) input-list)
          (T (my-member input-item (rest input-list)))))

It's done.  The function above does what we set out to do, and
you have to admit it was pretty darn easy to make it work.  In fact
it's so easy that many of you were telling me how to write this code
in class, and along the way you introduced the concept of recursion
in LISP without me having to prompt you (much).  We'll talk about
recursion a lot in the next couple of lectures, and you'll use it
a lot in the code you write.  But if you ever get weirded out by
recursion, stop and think about this example, and remember that 
it's such a simple concept that you introduced it in class before
I did.  Really.


Copyright 1998 by Kurt Eiselt.  All rights reserved.

Last revised: October 6, 1998