COMP 205 – Week 12 Dr. Chunbo Chu Last week Install Lisp in a Box loop read in an expression from the console; evaluate the expression; print the result of evaluation to the console; end loop. REPL Valid objects (S-expressions) • Atoms: numbers: (real 1.0, integer 1) symbols: a consecutive sequence of characters (no space) e.g., a, x, price-of-beef. two special symbols: T and NIL for logical true and false. strings: a sequence of characters bounded by double quotes e.g., "this is red". (Note: LISP is case insensitive) • Lists: a list of atoms and/or lists, bounded by "(" and ")“, e.g., (a b c), (a (b c)) top elements of a list example: top elements of list (a b c) are a, b, and c top elements of list (a (b c)) are a and (b c) nil: empty list, same as (). 2. Function calls • • • • also a list use prefix notation: (function-name arg1 ... argn) returns function value for the given list of arguments functions are either provided by Lisp function library or defined by the user. • Examples: >(+ 1 3 5) 9 >(/ 3 5) 3/5 >(/ 3.0 5) 0.59999999999999998 >(sqrt 4) 2 First Lisp program ( defun hello () (format t “Hello World!”)) Use Emacs to create a new file (C-x C-f)containing the above code. Save the file (C-x C-s) under name hello.lisp Switch back the SLIME buffer and load the program by: (load “hello.lisp) Run your program: (hello) Type q to leave the debugger if you have any error. first rest function nesting car cdr cadr caddr nthcdr butlast cons append Compositions of car and cdr This place ... Is equivalent to this place ... (caar x) (car (car x)) (cadr x) (car (cdr x)) (cdar x) (cdr (car x)) (cddr x) (cdr (cdr x)) Basic expression evaluation 2) Predicates (a special function which returns NIL if the predicate is false, T or anything other than NIL, otherwise) =, >, <, >=, <= for numerical values; predicates equal, eq, for others (symbols, lists, etc.) >(< x y) NIL >(= x y) T tests if x is a atom tests if x is a list >(equal ‘x ‘y) NIL >(equal ‘a (car L)) T >(atom x) T >(atom L) NIL >(listp x) NIL >(listp L) T >(atom (car L)) T also numberp, symbolp, null >(numberp ‘x) NIL >(numberp x) T >(symbolp ‘x) T >(symbolp x) NIL Basic storage handling >(null L) NIL >(null NIL) T >(null x) NIL 3) Set operations ( a list can be viewed as a set whose members are the top elements of the list) >(member 'b L) ; test if symbol b is a member (a top element) of L (B C) ; if yes, returns the sublist of L starting at the ; first occurrence of symbol b >(member ‘b (cons 'b L)) (B A B C) >(member x L) NIL ; if no, returns NIL Set operations >(union L1 L2) ; returns the union of the two lists >(intersection L1 L2) ; returns the intersection of the two lists >(set-difference L1 L2) ; returns the difference of the two lists Defining New Functions (defun name (parameter*) "Optional documentation string." body) Convention: you construct compound names with hyphens rather than underscores or inner caps. Thus, frob-widget is better Lisp style than either frob_widget or frobWidget. When a parameter list is a simple list of variable names, the parameters are called required parameters Optional Parameters Place the symbol &optional followed by the names of the optional parameters. (defun foo (a b &optional c d) (list a b c d)) When the function is called, arguments are first bound to the required parameters. (foo 1 2) (1 2 NIL NIL) (foo 1 2 3) (1 2 3 NIL) (foo 1 2 3 4) (1 2 3 4) Non-NIL default value Replace the parameter name with a list containing a name and an expression. The expression will be evaluated only if the caller doesn't pass enough arguments to provide a value for the optional parameter. (defun foo (a &optional (b 10)) (list a b)) (foo 1 2) (1 2) (foo 1) (1 10) More flexibility: (defun make-rectangle (width &optional (height width)) ...) Rest Parameters Functions need to take a variable number of arguments. E.g. (+), (+ 1), (+ 1 2), (+ 1 2 3), … A catchall parameter after the symbol &rest. Any arguments remaining after values have been doled out to all the required and optional parameters are gathered up into a list that becomes the value of the &rest parameter. (defun format (stream string &rest values) ...) (defun + (&rest numbers) ...) Keyword Parameters Suppose you have a function that takes four optional parameters. Now suppose that most of the places the function is called, the caller wants to provide a value for only one of the four parameters. After any required, &optional, and &rest parameters you include the symbol &key and then any number of keyword parameter specifiers. (defun foo (&key a b c) (list a b c)) (foo) (NIL NIL NIL) (foo :a 1) (1 NIL NIL) (foo :b 1) (NIL 1 NIL) (foo :c 1) (NIL NIL 1) (foo :a 1 :c 3) (1 NIL 3) (foo :a 1 :b 2 :c 3) (1 2 3) (foo :a 1 :c 3 :b 2) (1 2 3) (defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b))) Mixing Different Parameter Types Whenever more than one flavor of parameter is used, they must be declared in order : first the names of the required parameters, then the optional parameters, then the rest parameter, and finally the keyword parameters. Function Return Values The default behavior: return the value of the last expression evaluated as the function’s own return value. The RETURN-FROM special operator to immediately return any value from the function The first "argument" is the name of the block from which to return. (defun foo (n) (dotimes (i 10) (dotimes (j 10) (when (> (* i j) n) (return-from foo (list i j)))))) Data structure s assoc make-array aref defstruct Dotted pairs Dotted pairs 4) Conditional >(cond (<test-1> <action-1>) . . . (<test-k> <action-k>)) conditional • each (<test-i> <action-i>) is called a clause; • if test-i (start with i=1) returns T (or anything other than NIL), this function returns the value of action-i; else, go to the next clause; • usually, the last test is T, which always holds, meaning otherwise. • cond can be nested (action-i may contain (cond ...)) Now, having basic functions, defun and cond we can define any Lisp function. Examples. (heavy use of recursive definitions) (defun func-name (arg-1 ... Arg-n) func-body) 5. Define functions examples: member (defun member (x L) (cond ((null L) nil) ; base case 1: L is empty ((equal x (car L)) L) ; base case 2: x=first(L) (t (member x (cdr L))) ; recursion: test if x is in rest(L) )) (defun intersection (L1 L2) (cond ((null L1) nil) ((null L2) nil) ((member (car L1) L2) (cons (car L1) (intersection (cdr L1) L2))) (t (intersection (cdr L1) L2)) )) intersection Example: (intersection '(a b c) '(b a b c)) returns (a b c) (intersection '(b a b c) '(a b c)) returns (b a b c) (defun set-difference (L1 L2) (cond ((null L1) nil) ((null L2) L1) ((not (member (car L1) L2)) (cons (car L1) (set-difference (cdr L1) L2))) (t (set-difference (cdr L1) L2)) )) Define functions iteratively. dolist (dolist (x L result) body) • for each top level element x in L, do body(x); • x is not equal to an element of L in each iteration, but rather x takes an element of L as its value; dotimes (dotimes (count n result) body) ; do body n times. count starts with 0, ends with n-1 Note: result is optional, to be used to hold the computing result. If result is given, the function will return the value of result, returns NIL, otherwise. (may change global variables as side effects.) Activity Write a function sum to calculate the sum of all numbers in a list Various definitions of SUM (defun sum1 (L) (setq y 0) (dolist (x L y) (setq y (+ y x)))) (defun sum2 (L) (setq y 0) (dolist (x L y) (setq y (+ y (eval x))))) (defun sum3 (L) (setq y 0) (dotimes (count (length L) y) (setq y (+ y (nth count L))) )) >(setq L1 '(1 2 3)) (1 2 3) dolist >(sum1 L1) 6 defun sum4 (L) (setq y 0) (dotimes (count (length L) y) (setq y (+ y (eval (nth count L)))) )) dotimes >(setq L1 '(1 2 3)) (1 2 3) >(setq L2 '(a b c)) (A B C) >(dotimes (count 3) (set (nth count L2) (nth count L1))) NIL >a 1 >(sum1 L1) 6 >(sum1 L2) Error: … >(sum2 L2) 6 >(sum3 L1) 6 >(sum3 L2) Error: … >(sum4 L2) 6 if statement (if <test> <then> <else>) > (defun abs (a) (if (> a 0) a (- a))) ABS > (abs 2) 2 > (abs -3) 3 Logical operators and, or > (and NIL T) NIL > (and T 2 3) 3 > (or nil (= 5 4)) NIL > (or nil 5) 5 Recursion Recursive function definitions are very common in LISP > (defun factorial (num) (cond ((<= num 0) 1) (t (* (factorial (- num 1)) num)))) FACTORIAL > (factorial 4) 24 Variable Common Lisp is dynamically typed—type errors are detected dynamically. A variable can hold values of any type and the values carry type information that can be used to check types at runtime. Common Lisp is a strongly typed language in the sense that all type errors will be detected. (+ “hello” 1) Introducing variables (defun foo (x y z) (+ x y z)) LET special operator (let (variable*) body forms) Example: (let ((x 10) (y 20) z) ...) Within the body of the LET, the variable names refer to the newly created bindings. After the LET, the names refer to whatever, if anything, they referred to before the LET. Nested bindings (defun foo (x) (format t "Parameter: ~a~%" x) (let ((x 2)) (format t "Outer LET: ~a~%" x) (let ((x 3)) (format t "Inner LET: ~a~%" x)) (format t "Outer LET: ~a~%" x)) (format t "Parameter: ~a~%" x)) ; ; ; ; ; ; ; |<------ x is argument | | |<---- x is 2 | | | | |<-- x is 3 | | | The value of the last expression in the body is returned as the value of the LET expression. Binding forms Any construct that introduces a new variable name that's usable only within the construct is a binding form. More example? (dotimes (x 10) (format t "~d " x)) LET* (let* ((x 10) (y (+ x 10)) ) (list x y)) But not this (let ((x 10) (y (+ x 10))) (list x y)) Global variables Two ways to create global variables: DEFVAR and DEFPARAMETER. Global variables are conventionally named with names that start and end with *. (defvar *count* 0 "Count of widgets made so far.") The difference : DEFPARAMETER always assigns the initial value to the named variable DEFVAR does so only if the variable is undefined. A DEFVAR form can also be used with no initial value to define a global variable without giving it a value. Such a variable is said to be unbound. Other data structures Vectors come in two flavors Fixed-size vectors are a lot like arrays in a language such as Java: a thin veneer over a chunk of contiguous memory that holds the vector's elements. Resizable vectors, on the other hand, are more like arrays in Perl or Ruby, lists in Python, or the ArrayList class in Java: they abstract the actual storage, allowing the vector to grow and shrink as elements are added and removed. Vector To make fixed-size vectors containing specific values with the function VECTOR (vector) #() (vector 1) #(1) (vector 1 2) #(1 2) MAKE-ARRAY is more general than VECTOR since you can use it to create arrays of any dimensionality as well as both fixed-size and resizable vectors. One required argument to MAKE-ARRAY is a list containing the dimensions of the array. (make-array 5 :initial-element nil) #(NIL NIL NIL NIL NIL) Vector A resizable vector uses fill pointer to store the number of elements actually stored in the vector. (make-array 5 :fill-pointer 0) #() To add an element to the end of a resizable vector, use the function VECTOR-PUSH. The function VECTOR-POP returns the most recently pushed item, decrementing the fill pointer in the process. (defparameter *x* (make-array 5 :fill-pointer 0)) (vector-push 'a *x*) 0 *x* #(A) (vector-push 'b *x*) 1 *x* #(A B) (vector-push 'c *x*) 2 *x* #(A B C) (vector-pop *x*) C *x* #(A B) (vector-pop *x*) B *x* #(A) (vector-pop *x*) A *x* #() Vector To make an arbitrarily resizable vector, you need to pass MAKE-ARRAY another keyword argument: :adjustable. (make-array 5 :fill-pointer 0 :adjustable t) #() To add elements to an adjustable vector, you use VECTOR-PUSH-EXTEND, which works just like VECTOR-PUSH except it will automatically expand the array if you try to push an element onto a full vector. Activity Create an array of 5 elements, fill-pointer as 0 Use a loop to populate the array with 5 numbers Then calculate the sum of the numbers (defparameter *x* (make-array 5 :fill-pointer 0)) (defparameter *sum* 0) (dotimes (i 5 *x*) (vector-push i *x*)) (dotimes (i 5 *x*) (setq *sum* (+ *sum* (vector-pop *x*)))) Lisp input/output You can input/output data to: standard input/output, string or file A number of functions supported by the Lisp: (read) ;; reads the input from the standard input (print ‘a) ;; prints to the standard output (scanf…) (printf…) (format …) for formatted input and output (open ..) (close ..) for opening and closing the files (load ..) reads and executes the file The FORMAT Function (format T “Hello”) It takes two required arguments: A destination for its output: T, NIL, a stream, or a string T is shorthand for the stream *STANDARD-OUTPUT* NIL causes FORMAT to generate its output to a string, which it then returns. If the destination is a stream, the output is written to the stream. A control string that contains literal text and embedded directives. Any additional arguments provide the values used by the directives in the control string that interpolate values into the output. FORMAT Directives All directives start with a tilde (~) and end with a single character that identifies the directive. For example, the ~$ directive is used to print floatingpoint values. (format t "~$" pi) Some directives take prefix parameters, which are written immediately following the tilde, to control things such as how many digits to print. (format t "~5$" pi) the ~D directive used to output integers in decimal. The most general-purpose directive is ~A, which consumes one format argument of any type and outputs it in aesthetic (human-readable) form. Try the following: (format nil "The value is: ~a" 10) (format nil "The value is: ~a" "foo") (format nil "The value is: ~a" (list 1 2 3)) ~% emits a newline ~& a fresh line The difference between the two is that ~% always emits a newline, while ~& emits one only if it's not already at the beginning of a line. Activity Suppose you have a list (1 green ribbit) and you need to print the list like this: number color noise ------ -------- -------1 green ribbit The first column has a width of six; the second & third columns have widths of eight. The first column is prefixed by three spaces. The columns are separated by two spaces. (setq row (list 1 'green 'ribbit)) (format t "~& ~6D ~8A ~8A" (first row) (second row) (third row)) Files and File I/O Common Lisp provides a stream abstraction for reading and writing data and an abstraction, called pathnames, for manipulating filenames in an operating system-independent way. Functionality unique to Lisp such as the ability to read and write s-expressions. Reading File Data First, obtain a stream from which you can read a file's contents with the OPEN function. (open "/some/file/name.txt") OPEN returns a character-based input stream Use the object returned as the first argument to any of the read functions: READ-CHAR reads a single character; READ-LINE reads a line of text, returning it as a string with the end-of-line character(s) removed; and READ reads a single s-expression, returning a Lisp object. (let ((in (open "/some/file/name.txt"))) (format t "~a~%" (read-line in)) (close in)) To prohibit error when opening a non-existing file, use the keyword argument :if-does-not-exist to specify a different behavior. The three possible values are :error, the default; :create, which tells it to go ahead and create the file and then proceed as if it had already existed; NIL, which tells it to return NIL instead of a stream. (let ((in (open "/some/file/name.txt" :if-doesnot-exist nil))) (when in (format t "~a~%" (read-line in)) (close in))) The reading functions all take an optional argument. True (default): signal an error if they're called at the end of the file NIL: return the value of their third argument, which defaults to NIL (let ((in (open "/some/file/name.txt" :ifdoes-not-exist nil))) (when in (loop for line = (read-line in nil) while line do (format t "~a~%" line)) (close in))) File Output Need an output stream, which can be obtained by calling OPEN with a :direction keyword argument of :output. OPEN assumes the file shouldn't already exist and will signal an error if it does. Use :if-exists keyword argument to change that: :supersede tells OPEN to replace the existing file. :append causes OPEN to open the existing file such that new data will be written at the end of the file. :overwrite returns a stream that will overwrite existing data starting from the beginning of the file. NIL will cause OPEN to return NIL instead of a stream if the file already exists. (open "/some/file/name.txt" :direction :output :if-exists :supersede) File Output Data-writing functions: WRITE-CHAR writes a single character to the stream. WRITE-LINE writes a string followed by a newline, which will be output as the appropriate end-of-line character or characters for the platform. WRITE-STRING, writes a string without adding any end-of-line characters. Two different functions can print just a newline: TERPRI—short for "terminate print"—unconditionally prints a newline character, and FRESH-LINE prints a newline character unless the stream is at the beginning of a line. FRESH-LINE is handy when you want to avoid spurious blank lines in textual output generated by different functions called in sequence. Closing Files It's important to close files when you're done with them, because file handles tend to be a scarce resource. (let ((stream (open "/some/file/name.txt"))) ;; do stuff with stream (close stream) ) Problems... What if you forget to close an opened file? What if close has no chance to execute? A safer solution in Lisp WITH-OPEN-FILE (with-open-file (stream-var openarguments) body-forms) WITH-OPEN-FILE ensures the stream in stream-var is closed before the WITH-OPEN-FILE form returns. Examples: To read from a file: (with-open-file (stream "/some/file/name.txt") (format t "~a~%" (read-line stream))) To create a new file: (with-open-file (stream "/some/file/name.txt" :direction :output) (format stream "Some text.")) Activity Find a source program file of Lisp or other languages on your VM. Write a Lisp program to print the content of the source file.
© Copyright 2025 Paperzz