Features of Lisp ------------------ 1. Symbol manipulation and list processing. Lisp stands for list processing language. It is easy to manipulate lists and symbols in lisp. 2. Bottom-up programming. Lisp encourages a new style of programming called Bottom-up programming. Rather than starting with a problem and decomposing it into smaller modules, you think what kind of special programming constructs make it easy to implement your program. In other words you build a special-purpose language on top of Lisp, in which you implement your program. The same style can be extended to multiple layers of languages, each new layer bringing us closer to the application and away from Lisp. 3. Exploratory programming. Lisp also encourages easy experimentation due to its simple syntax, interpretive style, and the ability to test the subroutines or functions easily, as they are being developed. Hence the programmer need not design the whole program in advance before writing code. One can develop a part of the code, test it, and depending on how it goes, may choose to write the rest of the code in one way or another. Lisp is a good language to experiment with new programming language constructs as well. Lisp make it easy to create new constructs by writing macros. Macros makes it possible to create a high-level language which is better suited to a given domain much more easily than in other languages. This feature of Lisp is quite unique and powerful. 4. Functional programming. ``pure lisp'' is functional in that it does not have state. Functional programs do not have side effects, and hence are easy to reason about. They are also easy to parallelize. Even when Lisp allows programs with side effects, good lisp style is to carefully contain functions with side effects so that programs present a functional interface -- i.e., behave as if they do not have side effects. 5. Major AI language. Along with Prolog, C++, and Java, Lisp is one of the four major languages used in AI. Atoms and Lists ---------------- _______ Symbols, e.g., APPLE, A23 | | ________ Integers, eg., 2546 | | | | _____________ Atoms ---- Numbers _____ Rational numbers, eg 3/5 | | |________ Floating point numbers, eg.,3.4 | | | |________ Strings, eg., "APPLE" | | s-expressions | | |_________ | NIL = () | |--------- |_____________ Lists | |_________ other lists, (A 23 (C D)) Boxes and arrows representation. Each Cons cell is a 2-part box. (X Y) ________ ________ | | | | |\ | | X | --------->| Y | \ | |___|___| |___|__\| What about just ________ | | | | X | Y | |___|___| It is denoted as (X . Y) and is called a dotted pair. (X Y) is the same as (X . (Y)) (+ (+ X Y) 3) ________ ________ ________ | | | | | | | |\ | | + | --------->| | | -------->| 3 | \ | |___|___| |_|_|___| |___|__\| | | ________ ________ ________ ----> | | | | | | | |\ | | + | --------->| X | -------->| Y | \ | |___|___| |___|___| |___|__\| Evaluation ---------- Lisp is an interactive language. The interpreter is in Read-Eval-Print loop. 1. Constants evaluate to themselves. 2. Any symbol can also be variable and evaluates to its stored value (any type). 3. Lists are evaluated by applying the function represented by the first symbol of the list to the values of the remaining elements of the list. If X is set to 3, > (+ X 4) 7 4. Special forms can override the above "normal" evaluation rule. >(quote A) (abbreviated as 'A) A >(quote (A B)) (A B) >(SETQ A 3) 3 >A 3 Writing Programs ---------------- Nameless Functions -- also called Lambda expressions. (lambda (X Y) (abs (- X Y)); a function of two arguments X,Y that computes |X-Y|. > ((lambda (X Y) (abs (- X Y))) 2 3) 1 We need functions with names to be able to call them in many places. (defun abs-diff (x y) (abs (- X Y))) Loading and compiling >(load "filename") T >(abs-diff 2 3) 1 >(compile-file "filename") ; source compiled >(load "filename") ; object loaded. Built-in Functions ------------------ (SETF X 2 Y 3 Z 4) assigns X to 2, Y to 3, and Z to 4. Similarly SETQ. >(setf X '(A B C)) (A B C) >(first X) A >(rest X) (B C) >(first (rest (rest x))) C >(cons 'A x) (A A B C) second,third,nth,butlast,last,length,reverse,append,member,assoc etc. Pure lisp: No side-effects! Arithmetic functions: +,-,*,/,max,min Can take arbitrry number of arguments. > (- 5 2 3) 0 Predicates and conditionals --------------------------- A predicate is a function which returns a truth value: `true' represented by T and `false' represented by NIL. EQ and EQUAL: EQ checks if references are the same. EQUAL checks if contents are the same. = is used for arithmetic. EQL works like = for numbers and like EQ for other objects. >(EQ 'A 'A) T > (EQ '(A B) '(A B)) nil >(EQUAL '(A B) '(A B)) T <,>,<=,>= are available for comparison of numbers. null,atom,listp,numberp,symbolp,endp, etc. are predicates. OR,AND, NOT have usual meanings. Lazy Evaluation AND stops evaluating and returns the value of the first argument which evaluates to NIL. If there is no such argument, it returns the value of the last argument. Similarly OR. > (AND (print 3) (listp 'boy)) 3 ; printed value (also considered T) nil ; since boy is not a list, the result is nil. > (AND (listp 'boy) (print 3)) nil ; no printing this time, since the first ; argument itself evaluates to NIL. > (AND (listp nil) 5) 5 Conditionals ------------ (IF ) Use Progn to block a sequence of statements. (IF (listp (first x)) (progn (print (first x)) (rest x)) (progn (print (rest x)) (first x))) If there is no ELSE part you can use WHEN (no progn is needed). (WHEN ... ) If there is no THEN part you can use UNLESS (no progn is needed). (UNLESS ... ) COND is more versatile than IF, UNLESS and WHEN. (COND ( ... ) ( ... ) ... ( ... ) ) How is it evaluated? 1. Evaluates , , ... until one of them, say returns non-nil. 2. Evaluates , (CASE sex-of-child (male 'peter) (female 'alice) (otherwise (error nil))) If one of the keys is a list, the key-form is tested for membership in the list. The keys must be mutually exclusive. > (CASE x ((john jack peter) 'man) ((mary ann) 'woman) (t '(i donno))) Recursion --------- Much more natural in Lisp than iteration and is frequently used. (defun my-length (list ) (cond ((null list) 0) ; terminating case (t (+ 1 (my-length (rest list)))) ; recursive call on ; a substructure )) >(Trace my-length) my-length 1: (MY-LENGTH (A B C)) 2: (MY-LENGTH (B C)) 3: (MY-LENGTH (C)) 4: (MY-LENGTH NIL) 4: returned 0 3: returned 1 2: returned 2 1: returned 3 3 > (my-length '(A (B C D) E))? This function is singly recursive. At most one recursive call per each invocation. (defun count-atoms (list) (cond ((null list) 0) ((atom (first list)) (+ 1 (count-atoms (rest list)))) ; if the first element is atom, add 1 to the atoms in rest. (t (+ (count-atoms (first list)) ; otherwise add the number of atoms in the first of list (count-atoms (rest list)))) ; to the number in the rest of list. )) >(count-atoms '(A (B C) D)) 1: (COUNT-ATOMS (A (B C) D)) 2: (COUNT-ATOMS ((B C) D)) 3: (COUNT-ATOMS (B C)) 4: (COUNT-ATOMS (C)) 5: (COUNT-ATOMS NIL) 5: returned 0 4: returned 1 3: returned 2 3: (COUNT-ATOMS (D)) 4: (COUNT-ATOMS NIL) 4: returned 0 3: returned 1 2: returned 3 1: returned 4 4 This is doubly recursive. At most 2 calls for each invocation. Special-purpose parameters and local variables ------------------------------------------------ &Rest parameter --------------- Allows to write functions that take variable number of arguments: (defun my-max (&rest list) (my-max-aux list)) (defun my-max-aux (list) (cond ((null (rest list)) (first list)) ((> (first list) (my-max-aux (rest lits))) (first list)) (t (my-max-aux (rest lits))) )) This is doubly recursive and is inefficient. How inefficient? It needs O(2^n) comparisions! How to avoid redundant computation? Use local variables. Declare them using Let. (defun my-max-aux (list &aux temp) (let ((temp nil)) (cond ((null (rest list)) (first list)) ((> (first list) (setf temp (my-max-aux (rest lits)))) (first list)) (t temp) ))) In general, (let ((temp1) (temp2 t) (temp3 5) ....) .....) Lets can be nested and may declare variables with the same names. Static scope rules apply. let* is similar to let, except the variables are bound sequentially. > (let ((X 30) (Y (+ X 1))) > Y) Error: Attempt to take the value of the unbound variable `X'. > (let* ((X 30) (Y (+ X 1))) > Y) 31 Auxilary parameters are the same as local variables. They are declared with &aux. (defun my-max-aux (list &aux temp) ...) OR (defun my-max-aux (list &aux (temp nil) ...) Global variables Minimize these. Declare them using defvar or defparameter. Convention: encose them in **. (defvar **node-count** 0) ; does not change the value if already bound. (defparameter **node-count** 10) ; value can be changed. (defconstant e 2.7) ; can't be changed Optional parameters Sometimes we want to make the parameters optional. If not provided, they take on the default value. (defun root (x &optional (n 2)) (expt x (/ 1 n))) Keyword parameters If there are several parameters to the function and most of them take default values in any call, it is convenient to name them by keywords. (defun increment (x &key (amount 1)) (+ x amount)) To call this function with x=4 and amount = 2, we say > (increment 4 :amount 2). 6 Many built-in functions use keyword parameters, e.g., member, which returns the part of the second argument (a list) starting with the first argument (if it is in the second argument). >(member 'b '(a b c)) (B C) But ... > (MEMBER '(B C) '(A (B C)) NIL Why? Because MEMBER uses EQL to test for equality! The two lists are not the same according to EQL! We can make MEMBER use EQUAL instead of EQL for comparisons. We need to pass EQUAL as a keyword argument to member in order to do this. > (MEMBER '(B C) '(A (B C)) :test #'equal) ((B C)) TEST is a keyword parameter; #'EQUAL is a keyword argument. The #', which is called "Sharp-quote" tells lisp that the argument being passed is the function EQUAL, not just the symbol EQUAL. You can pass arbitrary user-defined functions as keyword arguments, not just built-in functions. > (MEMBER 3 '(2 5 6) :test #'<) (5 6) All kinds of parameters can be combined, but only in this order: Regular -- optional -- rest -- keyword -- auxilary. List Surgery ------------- List surgery refers to destructively modifying lists to efficiently update them without doing a lot of CONS'ing. List surgery is quite useful, but must be done very carefully, because the code written using list surgery might have unintended consequences. List surgery is done by using setf on non-variable forms. We have already seen a simple form of list surgery. For example, > (setf x (John Killed Mary)) (JOHN KILLED MARY) > (setf (first x) 'DAVID) DAVID > x (DAVID KILLED MARY) (setf (first x) 'DAVID) modified the contents of the first of the list x, to DAVID without copying the whole list. This idea can be used from inside a program to efficiently modify lists, for example in sorting. Consider a function "delete-first" which should take two arguments, an item and a list, and destructively modifies the list so that the first occurrence of item inside the list is deleted. > (setf x '(A B A B)) (A B A B) >(delete-first 'B x) (A A B) How would one write this function? (defun delete-first (item list) (cond ((null list) list) ((eq item (first list)) (rest list)) ((eq item (second list)) (setf (rest list) (rest (rest list))) ; destructive modification! list) (t (delete-first item (rest list)) ; recursive call list))) ; return the original list >(setf x '(A B A B)) (A B A B) >(delete-first 'B x) (A A B) >x (A A B) How does it work? It makes sense to see this in boxes and arrows notation: Initially: x \ \ ________ ________ ________ ________ | | | | | | | | | | |\ | | A | ---------> | B | -------->| A | ---------->| B | \ | |___|___| |___|___| |___|___| |___|__\| / / list After (setf (rest x) (rest (rest x))) x \ ___________________ \ ________ | _________ | ________ ________ | | | | | | | --->| | | | |\ | | A | ----- | B | -------->| A | ---------->| B | \ | |___|___| |___|___| |___|___| |___|__\| / / list Now consider what happens if we did : >(delete-first 'A x) (A B) So far, so good! But >x (A A B) Why? If you are deleting the very first item in the list, the function just returns the rest of the list! Lesson: destructive functions in general do not guarantee that their arguments are updated correctly! They only guarantee that they return the right values, and that they are destructive! Hence they should always be used with a setf. We would not have the previous problem, if we had simply said > (setf x (delete-first 'A x)) (A B) The lisp function delete deletes all occurrences of the item from the list. But if the item occurs as the first of list, it DOES NOT delete it! > (setf x '(A B A B)) (A B A B) > (delete 'A x) (B B) > x (A B B) NOTE : Destructive functions not only modify their arguments, but also modify anything that is pointing to sublists of them! Hence you should be very careful when you use destructive operations. You should make sure that there are no unintended updates to data which is pointing to the destructively modified objects. Data Structures --------------- Data structures provide useful abstractions that improve modularity and efficiency of code. (defstruct book (title ) ; The default value is nil. (author ) ; The default value is nil. (subject '(CS))); Note that the default is evaluated. 1. creates a make-book function (constructor) that should be called with keyword parameters which correspond to the field names. For example, > (setf x (make-book :title '(artificial intelligence) :author '(patrick henry winston) :subject '(AI))) #s(BOOK TITLE (ARTIFICIAL INTELLIGENCE) AUTHOR (PATRICK HENRY WINSTON) SUBJECT (AI)) 2. sets up the reader functions book-title, book-author, book-subject. E.g., > (book-author x) (PATRICK HENRY WINSTON) 3. Generalizes setf to behave as a writer function for all fields when used with the corresponding reader functions. E.g., > (setf (book-author x) '(stuart russell)) (STUART RUSSELL) > (book-author x) (STUART RUSSELL) DEFSTRUCT can be used to represent hierarchical structures through a primitive form of inheritance mechanism, called "include". E.g., > (defstruct employee (length-of-service 0) (payment-mode 'monthly)) EMPLOYEE >(defstruct summer-employee (:include employee (payment-mode 'bulk)) (period-of-employment '(3 months))) SUMMER-EMPLOYEE SUMMER-EMPLOYEE is a specialization of EMPLOYEE. So it has all the fields of EMPLOYEE in addition to period-of-employment, but its payment-mode defaults to BULK rather than MONTHLY. >(make-summer-employee) #s(SUMMER-EMPLOYEE LENGTH-OF-SERVICE 0 PAYMENT BULK PERIOD-OF-EMPLOYMENT (3 MONTHS)) The fields of structures are accessed efficiently by relative addressing! Moreover structures are automatically encapsulated. So you can't access or change the contents without using the accessor functions. When all you need is structured data items like the books of our book data base, then you might want to use defstruct. The data abstraction idea itself is much more powerful, and can be used to construct more elaborate abstractions, such as trees, stacks, data bases, and entire systems of interacting intelligent agents! Mapping functions ----------------- It is easy to write functions that take other functions as arguments. Lisp has many such built-in functions that take functional arguments. They are called mapping functions, e.g., mapcar. Suppose you want a list of the titles of all books in the list books. > (mapcar #'book-title books) ((ARTIFICIAL INTELLIGENCE) (MERCHANT OF VENICE) (THE PICKWICK PAPERS)) Suppose you want to collect the titles of only fictions. You first write a predicate fiction-p. > (defun fiction-p (book) ; to test if a book is a fiction (member 'fiction (book-subject book))) Now you can simply say: > (mapcar #'book-title (remove-if-not #'fiction-p books)) remove-if-not removes all members that do not satisfy the given predicate. mapcar then collects their titles. This is not a destructive function! Similarly other functions remove-if, count-if, reduce, etc. How to write your own mapping functions like mapcar? You need the ability to call a function which is passed as an argument. We use funcall for this. (funcall fn arg) is the same as calling the function which is bound to the argument fn on arg. >(funcall #'cons 'a '(b c)) (A B C). #' is called sharp-quote and tells lisp that it is a function. Now it is straightforward to implement a simple version of MY-MAPCAR that works with single-argument functions. (defun my-mapcar (fn list) (cond ((null list) nil) (t (cons (funcall fn (first list)) ; call the function passed as fn! (my-mapcar fn (rest list)))))) In general mapcar can work on n-input functions, by passing the function name and n lists of equal size. >(mapcar #'cons '(a b c) '((a) (b) (c))) ((A A) (B B) (C C)) The function APPLY is similar to funcall. It applies the function FN passed as its first argument to the list of remaining arguments. > (apply #'cons '(A (B C))) (A B C) But the following also works, because CONS is called on A and the elements of ((B C)), which is (B C). > (apply #'cons 'A '((B C))) (A B C) But note what happens when you do: > (apply #'cons 'A '(B C)) Error: CONS got 3 args, wanted at most 2. [condition type: PROGRAM-ERROR] This is because, now apply is called with A, B, C as the three arguments. Eval is similar in that it can be used to evaluate its argument. Since all functions evaluate their arguments once, EVAL effectively evaluates twice! > (eval '(CONS 'A '(B C))) is the same as (CONS 'A '(B C)) and returns (A B C) Iteration --------- DOTIMES: (defun factorial (n &aux (result 1)) (dotimes (i n result) (setf result (* result (+ i 1))))) How does it work: 1. The upper-bound form n is evaluated once before entering the body of the loop. 2. The count parameter i is initialized to 0. 3. If the count parameter is >= the value of the upper-bound form, result is evaluated and returned as the value of the loop. 4. Else the body is executed, i is incremented by 1 and the loop repeats. DOLIST - similar to DOTIMES, but iterates over the contents of a list. (defun my-reverse (list &optional result) (dolist (item list result) (push item result))) [= (setq result (cons item result))] Do is more general: Syntax: (DO (( ) ( ) ... ( )) ( ...) ) How does it work: 1. Before entering the loop, all the init-forms are evaluated SIMULTANEOUSLY and assigned to the corresponding parameters. 2. The termination test is evaluated. If it is non-null, then the forms following it are evaluated and the value of the is returned. 3. If not, the is evaluated. 4. All the parameters are assigned new values SIMULTANEOUSLY using the corresponding update forms. 5. Control goes to step 2. Example: Consider implementing factorial using DO. (defun do-factorial (n) (do ((i n (- i 1)) (result 1 (* result i))) ((= i 0) result) )) Let n = 3. The values taken by i and result are as follows when the test (= i 0) is evaluated: i result 3 1 2 3 1 6 0 6 -> returned One consequence of initializing and updating all parameters simultaneously is that the new values of the parameters are not used in the updates of parameters until the next iteration! In our case, for example, initializing result to i would not have worked, because i was being simultaneously given value along with result. As you might have guessed Do is usually implemented using Let to initialize its parameters. DO* is similar to Do except that it is implemented using LET* and hence initializes and updates all its parameters SEQUENTIALLY. Its syntax is the same as that of DO except for the keyword DO*. Syntax: (DO* (( ) ( ) ... ( )) ( ...) ) How does it work: 1. Before entering the loop, all the init-forms are evaluated SEQUENTIALLY and assigned to the corresponding parameters. 2. The termination test is evaluated. If it is non-null, then the forms following it are evaluated and the value of the is returned. 3. If not, the is evaluated. 4. All the parameters are assigned new values SEQUENTIALLY using the corresponding update forms. 5. Control goes to step 2. Common Lisp also has a general LOOP macro which can be as complex as the following example shows, and is very convenient. (LOOP for i from 1 to 10 while (< (* i i) 80) collect (* i i) do (print i) when (evenp i) do (print 'even) finally (print 'done)) This loop gets executed varying i from 1 to 10, as long as (* i i) < 80 is true. (square i) is collected in a list and returned as the loop's value. It also prints each such i, and prints EVEN when i is even. At the end, it prints DONE, and returns the list of squares of i. In the above case, it prints out: 1 2 EVEN 3 4 EVEN 5 6 EVEN 7 8 EVEN DONE (1 4 9 16 25 36 49 64) <- returned value! Structured loop is good, because it explicitly tells the program reader how it is controlled. Arrays ------ Lists only allow sequential access and are inefficient. We need arrays for random access. (make-array ) constructs an array. specifies how many dimensions are there and how big is each dimension. It must evaluate to a list whose size is the number of dimensions, and whose elements specify the size of each dimension. >(make-array '(2 3)) #2a((NIL NIL NIL) (NIL NIL NIL)) This denotes a 2 X 3 array whose elements are NIL. The number of dimensions of an array is called its rank. The indices of the array vary from 0 to n-1, where n is the size of the corresponding dimension. The above array can be represented in 2 dimensions as: second index 0 1 2 _________________ 0| NIL| NIL |NIL | first |_____|_____|____| index 1| NIL| NIL |NIL | |_____|_____|____| Suppose you want to represent a board position in Tic-Tac-Toe as a 3 X 3 array with the cells initialized to EMPTY. You say, (make-array '(3 3) :initial-element 'EMPTY) #2a((EMPTY EMPTY EMPTY) (EMPTY EMPTY EMPTY) (EMPTY EMPTY EMPTY)) If each element is not the same, the keyword parameters :initial-contents should be used. >(make-array '(3 3) :initial-contents '((cross circle empty) (circle empty cross) (empty empty empty))) #2a((CROSS CIRCLE EMPTY) (CIRCLE EMPTY CROSS) (EMPTY EMPTY EMPTY))) To access an array element, (aref ... ) Evaluates the to an array and retrieves the element in the array indexed by ... . >(setf array-1 (make-array '(2 3) :initial-contents '((A B C) (D E F)))) #2a((A B C) (D E F)) >(aref array-1 0 1) B >(aref array-1 1 0) D If indices are out of range, Lisp gives error. Setf can be used to write into array cells. >(setf (aref array-1 0 1) 'X) X >array-1 #2a((A X C) (D E F)) >(aref array-1 0 1) X In general, array cells can be changed by doing: >(setf (aref ... ) ) The , indices and are evaluated and the corresponding cell in the array is set to the result of . Input-Output and Strings ------------------------ So far the only way to input anything into the program has been through parameters, and the only way to output has been through the returned values and print statements. We now look at other ways of reading from and writing into terminals and files. Terminal input-output --------------------- > (print '(good bye)) (GOOD BYE) ; printed value (GOOD BYE) ; returned value Terminal input can be read with the read statement. Read, without arguments, reads from the terminal. > (setf X (read)) ; waits for the value of X. 30 ; typed in by the user. 30 ; returned value. > X ; ask to evaluate X 30 FORMAT can be used to control printing. > (FORMAT t "I like base-ball") | ----------------------> format string I like base-ball ; printed without quotes. NIL ; returned value. > (FORMAT nil "I like base-ball") "I like base-ball" ; returned value (with quotes). Anything that begins with a ~ in the format string is called a directive. It directs how the data that follows the format string (if any) should be printed. ~ followed by % tells it to start a new line. ~& starts a new line too, but only if its not already on a new line. So >(progn (format t "~%hello~%") (format t "~&how are you?")) ; blank line hello how are you? NIL Any number of items can follow the format string and they are all evaluated and printed as specified by the directives in the format string. ~a prints the next item in the list of items that follow the format string. ~na prints the next item in the list in n columns -- padding blanks to the right. (n should be a number). >(format t "~%Good Morning Mr. ~7a!" X) Good Morning Mr. SMITH ! ; (two blanks after SMITH) NIL ~nd prints integers in n columns -- padding blanks to the left. If no n is specified, it takes as many spaces as it needs. >(format t "age:~d sex:~a" 12 'male) age:12 sex:MALE NIL File operations --------------- To read from and write into files, you create streams. Input streams help read data from files. Output streams help write data into files. Streams are created using a open statement. Syntax: (open :direction :input (or :output or :io) :if-exists :overwrite (or :append or :error) ; only for ; output files :if-does-not-exist :error (or :create) ; only for input files ) e.g., (setf x (open "employees" :direction :input :if-does-not-exist :error)) x is assigned a stream object. The opened files need an explicit close statement to close. syntax: (close ) e.g., (close x) Read, Print, and Format can be used to work on streams. Syntax: (read ) (print ) (format ... ) Read reads printed representation of atoms, lists, and structures and returns it as value. On end-of-file, if is non-null an error is given; if it is null, is evaluated and its value will be the value returned. Strings and characters ---------------------- Strings and characters are data types and are considered "atoms". Strings are generally case-sensitive. However, they can also be compared with case-insensitive comparators. Case-sensitive comparators Case-insensitive comparators string> string-greaterp string< string-lessp string<= string-not-greaterp string>= string-not-lessp string= string-equal char takes two arguments, a string x and an integer n, and extracts n'th character from x. >(char "abc" 1) #\b Notice that the character index starts from 0 and increases to n-1. Note also that the characters are printed with a prefix #\ to distinguish them from atoms. char= is a case-sensitive equality checker. char-equal is a case-insensitive version of char=. >(char= (char "abc" 1) (char "bac" 0)) T >(char= (char "abc" 1) (char "bac" 0)) NIL The comparisons can also be made on substrings, using the keyword arguments, :start1, :end1, :start2, :end2. For example, >(string-lessp string1 string2 :start1 2 :end1 5 :start2 3 :end2 6) Compares only the characters indexed between 2 and 5 in string1 with the characters indexed between 3 and 6 in string2. Indices start from 0, and start and end are inclusive. If the selected substring of string1 is lexicographically smaller than the selected substring of string2, then the index of the first character in string1 on which the two substrings differ is returned. The returned index is always measured from the beginning of string1 starting with 0. If the former substring is not lexicographically smaller than the latter substring, NIL is returned. >(string-not-greaterp "abc" "acb") 1 >(string-not-greaterp "acb" "abc") NIL >(string-not-greaterp "ab" "abc") 2 >(string-not-greaterp "abc" "abc") 3 >(string-not-greaterp "abc" "ABC") 3 >(string-not-greaterp "abcdefgh" "aabcdghef" :start1 2 :end1 5 :start1 3 :end1 6) 4 >(string-not-greaterp "aabcdghef" "abcdefgh" :start1 2 :end1 5 :start1 3 :end1 6) 2 String-not-lessp works analogously. Use string<=, string>=, etc. for case-sensitive versions. The functions length and reverse work on strings too! They count the number of characters and reverse the characters in the string respectively. >(length "abcdef") 6 >(length "") 0 >(reverse "abcdefgh") "hgfedcba" ELT is another useful function that also works on a list or a string and returns the n'th element (character, atom or list) in it. >(elt '(a b c) 1) B >(elt "abc" 1) #\b Finally, individual characters in the input stream can be read by read-char. (read-char ) reads the next character in the input stream. (read-line ) reads the next line until the carriage return.