;;; This was a demonstratation of the difference ;;; between a function that returned a value of ;;; interest and a function that *printed* a value ;;; but returned nothing of interest. ;;; This function returns the smaller of its two arguments USER(1): (defun min1 (n1 n2) (if (< n1 n2) n1 n2)) MIN1 ;;; This function prints the smaller of its two ;;; arguments (but returns NIL just because ;;; FORMAT returns NIL). USER(2): (defun min2 (n1 n2) (if (< n1 n2) (format t "~a" n1) (format t "~a" n2))) MIN2 ;;; Here the function is evaluated and its ;;; return result is printed by the REPL. USER(3): (min1 3 4) 3 ;;; Here the FORMAT function first has ;;; the side effect of printing "3", but ;;; the return result is NIL USER(4): (min2 3 4) 3 NIL ;;; Of course with a return result you can ;;; _use_ the result in subsequent calculations. USER(5): (+ 1 (min1 3 4)) 4 ;;; In the second case MIN2 is first evaluated ;;; which causes the printing, but then its return ;;; result NIL is passed to the + function, which ;;; causes an error. USER(6): (+ 1 (min2 3 4)) 3 Error: NIL is an illegal argument to + [condition type: TYPE-ERROR] ;;; If you do want a function that prints the ;;; result of a calculation, the best thing ;;; to do is define two functions: one to ;;; calculate, one to print. Please do ;;; not confuse the two! USER(7): (defun print-min (n1 n2) (format t "Minimum is ~a~%" (min1 n1 n2))) PRINT-MIN USER(8): (print-min 3 4) Minimum is 3 NIL ;;; The question was asked in class whether it is ;;; _ever_ legitimate to have a PRINT or FORMAT ;;; statement inside of a calculating function, ;;; for example to print an error message. ;;; If you need to signal an error, the right ;;; thing to do is to call the ERROR function, ;;; which acts like FORMAT but then terminates ;;; the evaluation immediately. USER(10): (defun divide (n1 n2) (cond ((= n2 0) (error "Invalid denominator ~a" n2)) (T (/ n1 n2)))) DIVIDE USER(11): (divide 3 4) 3/4 ;;; Here notice that the error message is printed, ;;; then the [1] tells you we are in a break loop. ;;; We can use :ZOOM to examine the call stack to ;;; see that evaluation has been suspended. USER(14): (divide 6 0) Error: Invalid denomenator 0 [1] USER(15): :zoom Evaluation stack: (ERROR "Invalid denomenator ~a" 0) ->(COND . #) (DIVIDE 6 0) (EVAL (DIVIDE 6 0)) (TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP) (TPL:START-INTERACTIVE-TOP-LEVEL # # ...) ;;; Now on to LET and local variables. ;;; There was some question as to what this would ;;; print. What happens to the (+ x 4)? The answer ;;; is that it is evaluated, but it does not change ;;; the value of variable x. The body of the LET ;;; evaluates to the value of the _last_ form in ;;; the LET. USER(16): (let ((x 3)) (+ x 4) (- x 5)) -2 ;;; On the other hand, we _can_ side effect the variable ;;; using SETF. In this case X goes from 3 to 4. USER(18): (let ((x 3)) (setf x (+ x 1)) (- x 5)) -1 ;;; The variable X has lexical scope which means that ;;; we can only access its binding from within the ;;; textual scope of the LET body. Therefore at the ;;; top-level REPL, X is unbound. USER(19): x Error: Attempt to take the value of the unbound variable `X'. [condition type: UNBOUND-VARIABLE] ;;; Here is the notion of "shadowing." We will first give ;;; X a "global" value. USER(21): (setf x 3900) 3900 ;;; Now what happens when X is given lexical scope as well? ;;; First, references to X within the scope of the LET refer ;;; to the local version of X. Likewise for side effects: ;;; within the local scope, we are referring to the lexical ;;; variable only. ;;; The result is the same here as above, even though X ;;; now has a global binding. USER(22): (let ((x 3)) (setf x (+ x 1)) (- x 5)) -1 ;;; And also notice that the global binding for X does not ;;; change. USER(23): x 3900 ;;; This process of "shadowing" the bindings can be nested: ;;; here X has the global binding 3900 which is shadowed by ;;; the outer lexical binding of 3, which is in turn shadowed ;;; by the inner lexical binding of 4. Within the body of ;;; this inner LET, only the inner binding of X is accessible. USER(25): (let ((x 3)) (let ((x 4)) (+ x x))) 8 USER(26): USER(26): x 3900 ;;; Here's one we didn't do in class: USER(27): (PROGN (let ((x 3)) (let ((x 4)) (format t "Inner Lexical X is ~a~%" x)) (format t "Outer Lexical X is ~a~%" x)) (format t "Dynamic X is ~a~%" x)) Inner Lexical X is 4 Outer Lexical X is 3 Dynamic X is 3900 ;;; It's also worth noting that the formal parameter list ;;; in a function also creates a lexical variable: ;;; So in this case the change to the variable alters only ;;; the lexical variable and not the dynamic binding. USER(27): (defun negate (x) (setf x (- x)) x) NEGATE USER(28): (negate 3) -3 USER(29): x 3900 ;;; Here we have two lexical versions of X, ;;; one a parameter to NEGATE and one a ;;; parameter to DOUBLE-NEGATE. USER(30): (defun double-negate (x) (* 2 (negate x))) DOUBLE-NEGATE USER(31): (double-negate 5) -10 USER(32): x 3900 ;;; The LET special function allows you to ;;; create and initialize multiple lexical ;;; variables USER(33): (let ((x 3) (y 4)) (* x y)) 12 ;;; But these variables are not bound sequentially: ;;; the bindings of the variables cannot be accessed ;;; except within the LET body itself. Here was the ;;; unexpected output: I had intended to get an ;;; UNBOUND-VARIABLE X error when Y was initialized, ;;; but forgot that X had a dynamic binding. So what ;;; actually happened here was that Y was initialized ;;; to 3901, and X was bound to 3. USER(34): (let ((x 3) (y (+ x 1))) (* x y)) 11703 ;;; This is what I wanted you to see USER(35): (let ((z 3) (y (+ z 1))) (* z y)) Error: Attempt to take the value of the unbound variable `Z'. [condition type: UNBOUND-VARIABLE] ;;; Now back to associations, which now are DEFSTRUCTs. ;;; After we execute DEFSTRUCT, we have the function ;;; MAKE-ASSOC, which takes keyword arguments for the ;;; slots. USER(38): (defstruct assoc key values) ASSOC ;;; Be wary, structures print in rather idiosyncratic ;;; ways USER(39): USER(39): (make-assoc) #S(ASSOC :KEY NIL :VALUES NIL) ;;; Now we initialize the list of associations; ;;; the variable *fruit-colors* is now a list of ;;; five assocation structures. USER(40): (setf *fruit-colors* (list (make-assoc :key 'apple :values '(red green)) (make-assoc :key 'banana :values '(yellow)) (make-assoc :key 'pepper :values '(red green yellow)) (make-assoc :key 'blueberry :values '(purple)) (make-assoc :key 'cherry :values '(red white)))) (#S(ASSOC :KEY APPLE :VALUES (RED GREEN)) #S(ASSOC :KEY BANANA :VALUES (YELLOW)) #S(ASSOC :KEY PEPPER :VALUES (RED GREEN YELLOW)) #S(...) ...) USER(41): USER(41): (length *fruit-colors*) 5 ;;; We also get the function ASSOC-P which answers whether ;;; its argument is an instance of the ASSOC data type. USER(42): (assoc-p (car *fruit-colors*)) T ;;; Now the function to find the colors associated with ;;; a fruit name, which we translate to an abstract function ;;; to find the VALUES for a KEY within a list of ASSOCS. USER(43): (defun find-colors (fruit-name) (alist-values *fruit-colors* fruit-name)) FIND-COLORS ;;; Our first try is to used the built-in FIND function ;;; to find the key in the list. But this won't work ;;; because the INPUT-KEY is a symbol, and the elements ;;; in the list are ASSOCs, and FIND uses the function EQL ;;; to compare them. A symbol will never compare EQL ;;; to an instance of any structure. USER(44): (defun alist-values (alist input-key) (let ((find-result (find input-key alist))) (cond ((null find-result) NIL) (T (assoc-values find-result))))) ALIST-VALUES USER(45): USER(45): (find-colors 'apple) NIL ;;; This is really the root of the problem, finding ;;; a symbol in a list of structures. USER(46): (find 'apple *fruit-colors*) NIL ;;; On the other hand, finding an integer in a list ;;; of integers works just fine. USER(47): (find 4 '(2 1 34 4)) 4 ;;; The point here is that we want to compare the ;;; input symbol not against the ASSOC, but against ;;; the value of the KEY field of the assoc. USER(48): (setf an-assoc (car *fruit-colors*)) #S(ASSOC :KEY APPLE :VALUES (RED GREEN)) USER(49): (assoc-key an-assoc) APPLE ;;; The function FIND-IF is what we want: it ;;; takes a predicate and returns the first object ;;; in the list that the predicate returns non-NIL ;;; for. USER(50): (find-if 'f *fruit-colors*)