University of Wisconsin - Madison | CS 540 Lecture Notes | C. R. Dyer |
Kinds of expressions and how they are evaluated:
For example,
Evaluating 3 returns 3
Evaluating "Jane Doe" returns "Jane Doe"
Evaluating x returns the value bound to x or "error" if x unbound
Evaluating 'x returns x
Evaluating (+ 1 2) returns 3
Evaluating (+ (* 3 5) 2) returns 17
|--- string Ex. "Jane Doe" | |--- number Ex. 27, 1.44 | |--- atom ---|--- symbol Ex. hungry, x | | s-expression ---|--- list Ex. (elem1 elem2 ... elemn) | (+ 2 3) | ((name bud)(age 20)(parents (al peggy))) | |--- dotted pair Ex. (age . 20)
Binding is the process of associating a value with a (symbolic) atom, including allocating storage the first time the atom is referenced. Atoms are not declared or typed. The data bound to an atom has a type. All symbols are initially unbound.
Binding is usually done using the setf function:
(setf x 3) ; bind value 3 to symbol x (setf family '(al peggy bud)) ; bind the list ; (al peggy bud) to ; the symbol family
A list is an ordered sequence of 0 or more elements surrounded by parentheses. E.g., (a b c) is a list of length 3, (a ((b)) c) is a list of length 3, ((a b) c) is a list of length 2, and () is a list of length 0 called the empty list or NIL.
(first '(one two three)) --> one (first '((one) (two) (three))) --> (one)
(rest '(one two three)) --> (two three) (rest '(one)) --> () or, equivalently, NIL
(cons 'a '(b c)) --> (a b c) (cons'(a) '((b c))) --> ((a) (b c)) (cons 'a nil) --> (a) (cons 'a (cons 'b nil)) --> (a b)
(list 'a 'b '(c d)) --> (a b (c d))
(quote (a b c)) --> (a b c) '(a b c) --> (a b c) '(+ 1 2) --> (+ 1 2) 'orange --> orange
(append '(a b) '(c d)) --> (a b c d)
(atom 'a) --> T T is a special atom indicating "true" (atom '(a)) --> NIL NIL is used here to indicate "false" (not the empty list)
(endp '(a)) --> NIL (endp NIL) --> T
(equal 'a 'a) --> T (equal '(a b) '(a b)) --> T (equal '(a b) '(a (b))) --> NIL
(eq 'a 'a) --> T atoms are always unique (eq '(a) '(a)) --> NIL (setf x '(a)) (setf y x) (eq x y) --> T
(eql 'a 'a) --> T (eql 4 4) --> T (setf x (+ 2 2.3)) (eql 4.3 x) --> T
(= 4 4.0) --> T (= 'a 'a) --> Error: arguments must be of type number
(member 'b '(a b c)) --> (b c) (member '(a b) '(a b c)) --> NIL (member 'b '(a (b) c)) --> NIL (member '(a b) '(a (a b) c)) --> NIL (member '(a b) '(a (a b) c) :test #'equal) --> ((a b) c)
(setf lst '(a b c)) (and (member 'd lst) (member 'b lst)) --> NIL (and (member 'a lst) (member 'b lst)) --> (b c) (or (member 'a lst) (member 'd lst)) --> (a b c)
(defun my-second (lst) (first (rest lst)))
lst is the formal parameter which gets a value bound to it when the function is called. The body (a sequence of s-expressions) is evaluated and the result of the last expression evaluated is returned as the value of the function my-second
(defun insert-second (item lst) (cons (first lst) (cons item (rest lst))))
But the above doesn't work if the input list is empty, so change it to:
(defun insert-second (item lst) (if (null lst) (list item) ;; return a list of length 1 containing item (cons (first lst) (cons item (rest lst)))))
Another example using cond instead of if:
(defun age-group (person) (let ((n (age person))) ;; age is a function that returns the age of ;; person and then binds it to the local ;; variable n, which is defined for the body ;; of the let (cond ((< n 2) 'baby) ((< n 18) 'child) ((< n 120) 'adult) (t 'dead))))
Variables are lexically scoped by default in Lisp. Below is one way of defining functions lexically within the scope of a local variable called stack:
(let ((stack nil)) (defun push (item) (setf stack (cons item stack))) (defun pop () (prog1 (first stack) (setf stack (rest stack))))
The key to writing recursive functions is to define the recursion relation that describes for the general case how a problem can be solved in terms of a simpler problem of the same form plus some computation to solve the original problem from the solution to the simpler problem. The other thing to take care of is when the recursion relation is false; these are the termination, or base, cases that stop the recursion.
Some examples:
(defun fact (n) (if (= n 1) ; termination case -- 1! = 1 1 (* n (fact (- n 1))))) ; recursion relation: n! = n * (n-1)!
(defun fact (n) (fact-aux n 1) (defun fact-aux (n result) (if (= n 1) result (fact-aux (- n 1) (* n result))))
(defun my-length (lst) ;; return the number of top-level elements in a list lst (if (endp lst) ; base case - an empty list has length 0 0 (+ 1 (my-length (rest lst))))) ; recursion relation - a list is ; one longer than a list that has its ; first element removed
For comparison, an iterative version of length can be defined by
(defun my-length (lst) (let ((result 0)) (dolist (elem lst result) (setf result (1+ result)))))
or
(defun my-length (lst &aux (result 0)) (dolist (elem lst result) (setf result (1+ result))))
(defun count-all-elems (expr) (cond ((null expr) 0) ; an empty list has 0 elements in it ((atom expr) 1) ; an atom is exactly 1 element (t (+ (count-all-elems (first expr)) (count-all-elems (rest expr))))))
Notice that recursion is used here in two ways: first to recurse across the successive elements of a list (the second recursive call), and second to recurse down through all sub-lists, sub-sub-lists, etc. (the first recursive call).
(defun my-equal (expr1 expr2) (if (or (atom expr1) (atom expr2)) (eql expr1 expr2) (and (my-equal (first expr1) (first expr2)) (my-equal (rest expr1) (rest expr2)))))
Last modified October 3, 1996
Copyright © 1996 by Charles R. Dyer. All rights reserved.