Declarative Languages
Lecture #2

Purpose:  To introduce enough basic concepts that you can start to write simple programs for yourself

2.1 A glorified pocket calculator

Lisp systems are interactive. Type some lisp at the prompt - it will go away, think about it and hopefully return some lisp in exchange. Then it gives you another prompt and the process repeats itself. More formally, we are in a loop (known as the "read-eval-print" loop) which does the following:

  1. read in a lisp form
  2. evaluate this form
  3. print the result
Now, it happens that numbers always evaluate to themselves (self-evaluating). Therefore, when you type in a lisp number you will get that number back. Note by the way that lisp offers the following types of number To make our calculator a little bit more interesting, we can apply arithmetic operations to these numbers. The syntax for doing so is
  1. left parenthesis
  2. name of operator
  3. various arguments to that operator, each preceded by whitespace
  4. right parenthesis
For example:
        (+ 1 2 3)
 - in this case, the operator is  + and the arguments are the numbers 1, 2 and 3. Similarly, we have the operators (or, to be precise, functions) Note that you can mix types (e.g. integer and float) within a call to any of the above and lisp will (a) not complain and (b) do the intelligent thing on your behalf. For example, (+ 1/5 0.8) => 1.0

Note about fonts. Anything in fixed width font is lisp - either what you entered or what the lisp system returned. Anything else is just me talking. I use the symbol "=>" to mean "returns". So in the above, if you type in the lisp form (+ 1/5 0.8) and press the Return key, I claim that a lisp system will return and print out the number 1.0.

2.2 Nested calculations

We can nest one function call within another one, for example:
        (+ (* 2 3) (* 4 5 6)) .
There is an unambiguous rule for evaluating function calls, as follows:

  1. process the arguments to the function, in order ("from left to right")
  2. evaluate each argument in turn
  3. once all the arguments have been evaluated, call the original function with these values
  4. return the result
So, to evaluate (+ (* 2 3) (* 4 5 6))
  1. We start by noting that we have a call to the function + with arguments (* 2 3) and (* 4 5 6)
  2. We evaluate the first argument, namely (* 2 3)
    1. We note that this is itself a function call (the function is * and its arguments are 2 and 3)
    2. We must therefore evaluate the first argument to * - this is the number 2
    3. We next evaluate the second argument to * - this is the number 3
    4. We can now call the function * with arguments 2 and 3
  3. The value of the first argument to the function + is therefore 6
  4. Similarly the value of the second argument to the function + is 120
  5. We can now call the function + with arguments 6 and 120 and finally return the value 126.
When written out in full like this, it seems very long-winded and complicated, but it isn't really. Just bear in mind that lisp will evaluate everything in sight, going from left to right, and evaluating inner expressions before outer ones. Think of it as the obvious thing to do!

2.3 Interim summary

We have a uniform syntax for function calls:
        (function arg-1 arg-2 ....)

To evaluate a function call, lisp first evaluates all the function's arguments (in order) and then calls the function itself, with the evaluated arguments.

2.4 Some simple functions

float        takes one argument (of any numerical type) and returns the equivalent float.
list         takes any number of arguments (of any type / types) and returns a list of those arguments.
identity     takes one argument (anything) and returns it.

As we are beginning to see, the types of arguments to functions and of values returned by them are not necessarily tied down in advance. In lisp you do not have to declare types of variables, or what types a function expects to receive and return. This may take some getting use to, but it really does make programming considerably less painful :-)

Aside: there may be circumstances (optimizing inner loops) when you need to declare types, to get lisp to run that bit faster. A mechanism for this does exist but is outside the scope of this course.
2.5 Some examples of operators which are not functions

quote    takes one argument (anything) and returns it UNEVALUATED
defun    used for defining more functions - see below

Recall - a function evaluates all its arguments, using the rules described in 2.2 / 2.3 above. Always. Therefore, if an operator does not evaluate all of its arguments as per these rules, then it cannot be a function. (Logic.)

The operator quote suspends evaluation - it returns its argument as-is. For example:
        (quote (foo bar wibble))
=>     (foo bar wibble)
Therefore quote is not a function.

For another example, consider the difference between
        (quote (+ 1 2 3 4))
and     (identity (+ 1 2 3 4))
- identity is a function and therefore it evaluates its argument, whereas quote is not and does not.

Aside: lisp has two sorts of "not a function" - macros and special operators. When using one of these you do not ever need to know whether it was a macro or a special operator. The only distinctions between macros and special operators are that (i) macros always work by "transforming source code" - a bit like #define in C and (ii) you can define more macros yourself, but the set of special operators is fixed.
In fact, quote is a special operator. (Aren't you glad you know that?) To see the value of being able to quote constructs in a language, visit http://www.cob.ohio-state.edu/~tomassin/whotext.html (or, if terribly enthusiastic, read "Godel, Escher, Bach" by  Douglas R. Hofstadter or at least http://www.ncsu.edu/felder-public/kenny/papers/godel.html).

Finally note the following shorthand for quote:
        '(foo bar wombat)
is the same as
       (quote (foo bar wombat))

The ' character is sometimes referred to as syntactic sugar - it's there to make the syntax that bit sweeter but doesn't add anything that couldn't be done another way. Nevertheless, lisp programmers are lazy (the language encourages this) and you'll never find them typing the symbol quote in full. Ever.

2.6 Defining your own functions

To define a function of your own, use the macro defun. Examples:

;;; returns the number of days in a non-leap year
(defun days-in-year ()                ; no arguments
  (+ (* 30 4)
     (* 31 7)
     28))

;;; takes one argument and returns a list containing that argument twice
(defun list-of-two (thing)            ; one argument
  (list thing thing))

Note: 2.7 Beauty tips

A few stylistic layout conventions (please observe them rather than inventing your own, as they will make your code more readable both for you and for the examiners):

Comments: 2.8 Lists

As you will probably have spotted by now, a list is any collection of objects with a left parenthesis at one end and a right parenthesis at the other. For example,
    (round the ragged rocks the rugged rascal ran)
Lists can be of any length, and can hold data of mixed type (as in the example below).

Lisp code is built with lists. Lists can also be used to hold program data.

The function list can be used to build lists, e.g.
    (list "my" (+ 1 2) 'sons)           =>  ("my" 3 SONS)

To get at the contents of a list, use the functions first, second, third,... (up to tenth). So:
    (second (list "my" (+ 1 2) 'sons))  =>  3
To "drop the front" off a list, use the function rest:
    (rest (list "my" (+ 1 2) 'sons))    =>  (3 SONS)
 

2.9 The empty list and nil

A list can be empty - in other words it doesn't contain anything at all. You would type it like this:
    ()
but the weird thing is that when you do that, lisp stores it (and, indeed, prints it) not as a list but as the symbol:
    nil

This is because the empty list () and the symbol nil are the same object. .

Be aware that nil is self-evaluating, so
    nil => NIL
    ()  => NIL           ; not the usual rule for evaluating lists

2.10 True or false?

To add to the fun, nil is also used by lisp to denote logical falsehood.

Anything which is non-nil denotes logical truth. To prevent a total free for all, functions which are required to answer "true" or "false" (predicates) usually return the symbol t to denote truth.

Stylistic issue: the names of lisp predicates generally end in the letter p. If the name contains hyphens already, then hyphenate the p also. So:
        zerop
        array-has-fill-pointer-p

Example:

CL-USER 1 > (defun equal-to-fourty-two-p (number)
              ;; is number equal to 42?
              (= number 42))
EQUAL-TO-FOURTY-TWO-P

CL-USER 2 > (equal-to-fourty-two-p 15)
NIL

CL-USER 3 > (equal-to-fourty-two-p 42)
T

CL-USER 4 >

Note here the predicate =, which is an immediate exception to the above naming convention (sorry about that). This function takes any number of arguments - so long as they are all numbers - and returns t provided they are all numerically the same:
    (= 3 3.0)  =>   T
    (= 5/2 2.5)  =>  T
    (= 3 3 3 3 3)  =>  T
    (= 3 3 17 3 3)  =>  NIL

2.11 If...

The syntax for the operator if is

  1. if
  2. predicate (always evaluated)
  3. what to do if the predicate is true, ie if the predicate does not evaluate to nil (this argument is not evaluated if the predicate is false)
  4. what to do if the predicate is false, ie if the predicate evaluates to nil (this argument is not evaluated if the predicate is true)
For example:
(if (saucepan-is-cool-enough-p)
    (pick-it-up)
  (let-it-cool-down))
It's just as well that if is not a function (if it were, you'd always get burned).

Another example:

;;; Given an integer n and a list,
;;; return the nth (zero based) member of list.
;;; Make it easier by assuming list is short.
(defun nth-member (n list)
  ;; there has to be a better way... (we hope)
  (if (= n 0)
      (first list)
    (if (= n 1)
        (second list)
      (if (= n 2)
          (third list)
        "etc"))))
2.12 Summary 2.13 How to put this to use

Let's take as an example the following sample problem. You may want to work through this one carefully before moving on to the following exercises.

You may find that the following steps are helpful:
  1. Come up with a name for the function before you attempt to do anything else. Hmm, so how about sum-of-squares ?
  2. Make sure you're clear what calls to this function would look like and what value would be returned. We get this information directly from the statement of the problem: the function takes two (number) arguments, squares each one, adds these together and returns the result. So a call would look like (for example)
  3. and the return value in this case should be 25.
  4. Decide on names for the arguments. In this case, I would suggest something like first-number and second-number or, for brevity, first and second.
  5. Now you can write the "outer" part of the function:
    1. (defun sum-of-squares (first second)
        )
    With this much written, you should be happy with the statement that our sample call would execute the (as yet unwritten) body of this function with first having the value 3 and second having the value 4
  6. Next you have to write the function calls which will take two values and evaluate the sum of their squares. The square of the first number is expressed thus:
    1. (* first first)
    (and similarly for the square of the other), and the sum of the two quantities is therefore:
  7. Now you can slot this into the function template you wrote earlier:
    1. (defun sum-of-squares (first second)
        (+ (* first first)
           (* second second)))
    and type this into a lisp listener.
  8. Finally you test that the function works, by typing in sample calls and checking that the correct answer is returned.
2.14 Suggested activity / exercises etc
  1. Read Graham to page 18
  2. The functions + - * / = list all take a variable number of arguments. Experiment to find out what each of these does with exactly one argument. Now try each function with zero arguments, e.g. (+) and find out what happens.
  3. As in 2.2. above, describe in detail what happens when lisp evaluates each of the following
  4. Write a function my-identity which takes one argument and returns that value. Sample call:
    1. (my-identity "anything")  => "anything"
  5. Write a function which takes two arguments and returns them in a list, second argument first.
  6. Write a function which takes one argument, guaranteed to be a list of precisely two elements, and returns a new list which contains both these elements but in reverse order.
  7. Write a function which takes three numbers as separate arguments and computes their average
  8. Write a function which takes a list of three numbers (i.e. one argument), and computes the average of these numbers
  9. Write a function called minutes-in-one-year which takes one argument leapp. If leapp is true (i.e. non-nil) the function returns the number of minutes in a leap year; if leapp is false (i.e. nil) then it returns the number of minutes in a non-leap year.
  10. Given that lisp defines first, second,..., tenth for you, how would you define eleventh?
If you get seriously stuck, look at the solutions but please have a good go at each problem first.
Nick Levine
                                                                               last modified 2000-09-13
                                                         Copyright (C) Nick Levine 1999-2001. All rights reserved.
$Id: //info.ravenbrook.com/user/ndl/lisp/declarative/lectures/lectures/lecture-2.html#2 $