Declarative Languages
Lecture #3

Purpose: To review carefully old ground, and then pursue a little further some of the concepts which will allow you to write more interesting functions

3.1 Review - statement of lisp evaluation rules
 
By evaluate we mean different things for different lisp forms...
  • To evaluate a self-evaluating object such as a number or a string, simply return the value of that number or string. So:
    • 3.141592653589793 => 3.141592653589793
    • "Hi Nick" => "Hi Nick"
  • To evaluate a symbol, use the value associated with that symbol. So:
    • a symbol used to name an argument to a function behaves as a program variable in the obvious way. For example, if we have
      • (defun wombat (this that other) ...)
      • (wombat "foo" "bar" "wibble")
      then within the body of the function wombat, the variable this has value "foo",  and so on.
    • some symbols have globally accessible, constant (meaning: you aren't allowed to change them) values:
      • nil  =>  nil
      • t  =>  t
      • pi  => 3.141592653589793
  • To evaluate a list
    • If it is a function call then evaluate each of its arguments, in order, and call the function with the results of these evaluations. The result of the evaluation is whatever value the function returns. So far we have met the following functions:
      • + - * / = 
      • list first second ... tenth rest
      • identity
      • float
    • If it is not a function call, we treat it specially, according to the idiosyncratic definition of that particular non-function. Examples of non-functions include:
      • quote (suspends function evaluation)
      • defun (defines new functions)
      • if (conditional evaluation)
      For example, if has syntax (if condition then-form else-form). (Actually the else-form is optional.) It always evaluates the first subform condition. If that is true (i.e. non-nil) then the second subform then-form is evaluated and the resulting value returned by if; otherwise the third subform else-form - if supplied - is used. So
        (if nil
            (hideous-disaster)
          "success")
      is quite safe.

3.2 Review - anatomy of a function call

Please do not hold your breath for the entire duration of this section...

We are going to revise the story so far by looking at the simple lisp session below and examining each step in minute detail.

CL-USER 1 > (defun rev-list-of-2 (my-list)
              (list (second my-list)
                    (first my-list)))
REV-LIST-OF-2

CL-USER 2 > (rev-list-of-2 '(foo bar))
(BAR FOO)

CL-USER 3 >

We start by recalling the top-level read-eval-print loop, in which lisp repeatedly performs the following tasks:
  1. print a prompt
  2. read a lisp form
  3. evaluate that form
  4. print the result
So, the lisp system starts by printing the first prompt CL-USER 1 > and waits for a complete lisp form to be typed in. It then reads in the complete form (defun ...) and proceeds to evaluate it. Since defun is not a function it is handled specially, which means here that we are going to define a new function, in this case called rev-list-of-2. The name of the function is returned by defun to the top-level loop, this return value is printed out and a new prompt CL-USER 2 > is issued.

We recklessly decide  to call our function with some test data. Looking at the function definition, we can see that it takes precisely one argument, named here my-list, and (given that we are handing that value on to functions like first) this argument has to be a list. The list we want to use for test data has two members: foo and bar. How do we construct this list? Several suggestions come to mind, but not all of them work:

Suppose (for the sake of argument) we go for the first option, and type in the form
        (rev-list-of-2 '(foo bar))
Lisp reads this in and proceeds to evaluate it. It knows that rev-list-of-2 is a function, so the system has to evaluate all the arguments supplied, before it can proceed with the function call. In this case there is one argument, '(foo bar), which evaluates to (foo bar), and so rev-list-of-2 is called with my-list set to the list (foo bar).

The body of the function is the form (list (second my-list) (first my-list)). We evaluate this by appealing yet again to the evaluation rules above. list is a function, so we start by evaluating its arguments. The first argument is the form (second my-list) - i.e. a call to the function second. This call to second has to (guess what!) evaluate its argument; the argument is the variable my-list whose value is (foo bar) and so second is called with argument (foo bar) and it duly returns the value bar. Therefore the first of the two values which we are going to pass to the function list is the symbol bar. Similarly the second value passed to list is the symbol foo, and the call to list returns the list
        (bar foo)
which is finally printed out to the sound of massive celebrations by all concerned.

(Did you remember to keep breathing?)

3.3 defun reconsidered

We recall that the syntax of defun is
        (defun name (arg-1 arg-2 ...) form-1 form-2 ... form-n)
where name is the name of the function, arg-1 etc. are the names corresponding to the arguments to that function, and form-1 etc. are the lisp forms to evaluate in order to process a call to that function. The value returned by the final form form-n is the value to be returned by the call to the function itself.

Within the body of the function (i.e. during the evaluation of the forms form-1 form-2 etc) the names arg-1 arg-2 etc. are available to you as program variables. Their initial values are the results of evaluating the arguments to the function; in lisp parlance we say that these variables are bound to those values.

So if we have

(defun square (x)
  (* x x))
then during a call (square (+ 1 1)), the variable x is bound to the result of evaluating (+ 1 1).

The binding lasts only while the function is active. It ceases to exist on exit from the function and is not visible in calls to subfunctions (shallow binding); the following won't work...

(defun foo (wombat)
  (bar))

(defun bar ()
  wombat)

(Question to check you're still awake: how would you get the variable wombat into the function bar?)

3.4 More variables still

OK, so we know how to create variables on entry to functions and how to assign them values. Suppose we want to create further variables within the body of a function? We can do this by means of the special operator let*. For example, instead of

(defun add-one-and-square-it (x)
  (* (+ x 1) (+ x 1)))
we could write
(defun add-one-and-square-it (x)
  (let* ((x-plus-one (+ x 1)))
    (* x-plus-one x-plus-one)))
The syntax of let* is

    (let* ((var-1 value-1)
           (var-2 value-2)
           ...)
       form-1
       form-2
       ...
       form-n)

Each of the pairs (var-i value-i) represents a variable binding - the expression value-i is evaluated and the variable var-i is bound to that value. The bindings are done in order:

  1. evaluate value-1
  2. bind var-1
  3. evaluate value-2
  4. bind var-2

  5. etc
and so on. (This means that, for example, you can use var-1 in the evaluation of value-2.) The bindings last until the end of the let* statement, at which point let* returns the value of the final form, form-n.

3.5 Example - reading, writing and arithmetic

Let's trot out the example of converting temperatures from Fahrenheit to Celsius. Rather than writing a function which takes one value and returns another, i.e.

CL-USER 18 > (defun simple-fahrenheit-to-celsius (fahrenheit)
               (float (* (- fahrenheit 32) 5/9)))
SIMPLE-FAHRENHEIT-TO-CELSIUS

CL-USER 19 > (simple-fahrenheit-to-celsius 212)
100.0

CL-USER 20 > (simple-fahrenheit-to-celsius 32)
0.0

CL-USER 21 > ; very happy

suppose that we have been asked to write a function which prompts the user for a value, reads it in and prints the result.
CL-USER 31 > (fahrenheit-to-celsius)
Please give a value in degrees F: 32
32 degrees F is 0.0 degrees C.
NIL

CL-USER 32 > ; etc

We need the following functions to handle reading and printing of lisp values:
read reads a lisp form and returns it. At its simplest, a function of no arguments which reads from the listener. If no form has been typed yet, read will sit and wait until a complete form is available to it.
format  function for generating formatted output. First argument is a destination. Specify
  • nil for output to a string (like sprintf in C), which format generates and returns.
  • t for output to the listener (like printf in C), in which case format returns nil.
The next argument is known as the format string. In the same way that printf handles specially any occurrence of the character %, format handles specially any occurrence of the character ~ (pronounced tilde).  In particular,
  • ~& means: output a fresh line (i.e. if we weren't already at the start of a line, output a new one)
  • ~a means: take the next of the arguments to format and insert its printed representation here
Example: (let* ((name "Nick")) (format nil "Hello, ~a." name)) 
    =>  "Hello, Nick."

And the function definition looks like this:

(defun fahrenheit-to-celsius ()
  (format t "~&Please give a value in degrees F: ")
  (let* ((fahrenheit (read))
         (celsius (float (* (- fahrenheit 32) 5/9))))
    (format t "~&~a degrees F is ~a degrees C.~&" fahrenheit celsius)))


3.6 Further example - quadratic equations

If a quadratic equation is something you "know from nothing", don't panic. All we want to do is define a function of three numerical arguments a, b and c which returns a list of the following two values:

(assuming the square-root is possible - if it isn't we'll generate an error instead).
Don't ask why, just assume your lecturer has acquired a passion for daft formulae and let him get on with it.

Anyway, this example introduces three new functions:
 
< predicate which takes any number of (numerical) arguments and returns t if they are all in strictly ascending order. Otherwise it returns nil. So 
      (< 499 499.8 500 1000)  => t
      (< 1 2 3 4 6 5 7 8 9)  => nil
      (< 0 0)  => nil
While we're here, > tests for descending order, and <= and >= are variants of < and >  which allow arguments which are =
      (>= 2 1 1 1 0 0)  => t
sqrt function of one number which returns its square root.
error function for flagging (signalling) errors. At its simplest, it is happy with a single argument (a string) which will be printed out as an error message.

So here we go:

(defun quadratic-solve (a b c)
  (let* ((discriminant (- (* b b) (* 4 a c))))
    (if (< discriminant 0)
        (error "Discriminant was negative.")
      (list (/ (+ b (sqrt discriminant))
               (* -2 a))
            (/ (- b (sqrt discriminant))
               (* -2 a))))))
or (imo better - why?)
(defun quadratic-solve (a b c)
  (let* ((discriminant (- (* b b) (* 4 a c))))
    (if (< discriminant 0)
        (error "Discriminant was negative.")
      (let* ((square-root (sqrt discriminant))
             (denominator (* -2 a)))
        (list (/ (+ b square-root) denominator)
              (/ (- b square-root) denominator))))))


3.7 Further Logic

Some of the examples below will need the following pair of macros.
 
and Takes any number of arguments. Evaluates them in order until one returns nil, at which point and stops evaluating things and returns nil. If the last argument returns non-nil, then and returns that value. Examples:
  • (and 1 2 3 4 5)  =>  5
  • (and 1 2 nil 4 5)  =>  nil
  • (and nil (global-meltdown))  =>  nil  (without the meltdown)
or Takes any number of arguments. Evaluates them in order until one returns a non-nil value, at which point or stops evaluating things and returns that value. If the last argument returns nil, then or returns nil. Examples:
  • (or 1 2 3 4 5)  =>  1
  • (or nil nil nil 4 nil 5 6 7)  =>  4
  • (or nil (global-meltdown))  =>   some sort of regrettable disaster

3.8 Practical Session

  1. Lisp defines a function not, which maps nil to t and any other value to nil.  Define your own function (call it my-not) which has this behaviour:
    1. (my-not nil)  =>  t
      (my-not anything-else)  =>  nil
    If this gives you any difficulty, start by defining a function which takes one argument, returns t if the argument is non-nil, and nil if the argument is nil. Do not forget the equivalence between the empty list, the symbol nil and logical falsehood.
  2. Write:
    1. A function of two arguments (both numbers) which finds the smallest. Test carefully (e.g. if the numbers are equal does it still work?)
    2. (A little trickier...) Now do the same with three numbers (again test this carefully).
  3. Experiment to see what happens if you attempt to rebind a constant, both by using it as the name for an argument to a function, and by using let*.
  4. Implement celsius-to-fahrenheit.


3.9 Suggested activity / exercises etc

  1. Go through the above notes and answer all the rhetorical questions.
  2. (Only try this one if you've met complex numbers in maths...) Take the square-root of a negative number and see what happens. Note that what you have in front of you is valid lisp as far as the reader is concerned (type it back in to prove that). Now generate the square of that object. (Advanced topic: Did you get back to where you started? Why / why not?)
  3. Why does the call to fahrenheit-to-celsius above print a NIL?
  4. We could have written
    1. (defun fahrenheit-to-celsius ()
        (format t "~&Please give a value in degrees F: ")
        (format t "~&This is ~a degrees C.~&"
                (float (* (- (read) 32) 5/9))))
    but that would not have been as elegant a solution. Why?
  5. On the other hand we might have taken this one step further and written
    1. (defun fahrenheit-to-celsius ()
        (format t "~&Please give a value in degrees F: ~&This is ~a degrees C.~&"
                (float (* (- (read) 32) 5/9))))
    but that would not have worked properly. Why? (Guess before you try it out.)
  6. I mentioned in lecture 2 that macros work by transforming source code into something more "fundamental" (albeit longer and less readable). For example, we could transform the call
    1. (and foo bar)
    into
      (if foo bar nil)
    Figure out, in terms of code which does not use any macros (hint: if is not a macro, it's a special operator, so you can use it), what each of the following could be transformed into:
    1. (and foo bar baz)
    2. (or foo bar)   (Answer not 100% obvious. See solutions sheet now for hint.)
    3. (or foo bar baz)
  7. Now pick one of the above four examples (the hairiest you feel you can cope with) and write code to do the transformation for you. E.g.

  8.     (defun transform-and-with-2-args (form) ...)
        (transform-and-with-2-args '(and foo bar))  =>  (if foo bar nil)
  9. Advanced topic for survivors of the quadratic equation example: implement a function which solves simultaneous equations. I.e. given the numbers a b c d e f find a pair of numbers x and y which satisfy the equations
    1. ax + by = c     and       dx + ey = f
    You will have to test for one particular special case, to avoid division by zero. (What happens if you don't?)
If you get seriously stuck, look at the solutions but please have a good go at each problem first.

Coming next: more about lists....
 

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