![]() |
![]() | |||||||||||||||||||||||||||||||||||||
|
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]()
|
Introduction and Overview LISP stands for LISt Processing and is a functional, or applicative, programming language based on John McCarthy's work on numerical computation, published in 1960. Common LISP is a dialect of LISP, a successor to MacLISP, influenced strongly by Zetalisp and to some extent by Scheme and Interlisp. GNU Common LISP (gcl) is a highly portable implementation of Common LISP originally based on Austin Kyoto Common LISP (AKCL), a Common LISP (CLtL1)1 implementation developed at Kyoto University in Japan and extended by Bill Schelter of UT/Austin. The gcl utility is intended to eventually support the ANSI Standard for Common LISP. Similar to gpc for the Pascal language, gcl generates C code which it compiles with the local optimizing C compiler (gcc). Learning ObjectivesThe purpose of this lesson is to introduce you to the basic structure and syntax of LISP programs. At the end of this lesson, you should be able to:
What is a "functional programming language"?Opinions differ, even within the functional programming community, on the precise definition of what constitutes a functional programming language. However, here is a definition that, broadly speaking, represents the kind of languages that are discussed in your text:
For example, consider the task of calculating the sum of the integers from 1 to 10. In an imperative language such as C, this might be expressed using a simple loop, repeatedly updating the values held in an accumulator variable total and a counter variable i:
total = 0;
for (i=1; i<=10; ++i)
total += i;
In a functional language, the same program would be expressed without any
variable updates. For example,
in Haskell2,
the result can be calculated by evaluating the expression:
sum [1..10]Here, [1..10] is an expression that represents the list of integers from 1 to 10, while sum is a function that can be used to calculate the sum of an arbitrary list of values. The same idea could also be used in (strict) functional languages such as SML3 or Scheme, but it is more common to find such programs written with an explicit loop, often expressed recursively. Nevertheless, there is still no need to update the values of the variables involved: SML:
let fun sum i tot =
if i=0 then tot else sum (i-1) (tot+i)
in sum 10 0
end
Scheme:
(define sum
(lambda (from total)
(if (= 0 from)
total
(sum (- from 1) (+ total from)))))
(sum 10 0)
LISP:
(defun sum (a b) (cond ((> a b) 0) (t (+ a (sum (+ a 1) b))))) (sum 10 0)It is often possible to write functional-style programs in an imperative language, and vice versa. It is then a matter of opinion whether a particular language can be described as functional or not.
What is the difference between Scheme and Common LISP?Scheme is a dialect of LISP that stresses conceptual elegance and simplicity. It is specified in R4RS (now R5RS) and IEEE standard P11784. Scheme is much smaller than Common LISP; the specification is about 50 pages, compared to Common LISP's 1300 page draft standard.Scheme is often used in computer science curricula and programming language research, due to its ability to represent many programming abstractions with its simple primitives. Common LISP is often used for real world programming because of its large library of utility functions, a standard object-oriented programming facility (CLOS), and a sophisticated condition handling system. Because of its "real world" popularity, we will focus on GNU Common LISP in this document and in the course. The Pratt and Zelkowitz text emphasizes the Scheme dialect of LISP, although it does include some examples from Common LISP. This means that the examples shown in your textbook may not run with the GNU gcl utility installed on the class server. History of LISPLISP is a family of languages with a long history. Early key ideas in LISP were developed by John McCarthy during the 1956 Dartmouth Summer Research Project on Artificial Intelligence. McCarthy's motivation was to develop an algebraic list processing language for artificial intelligence work. The primary dialect of LISP between 1960 and 1965 was LISP 1.5. By the early 1970's there were two predominant dialects of LISP: MacLISP and Interlisp.MacLISP improved on the LISP 1.5 notion of special variables and error handling. MacLISP also introduced the concept of functions that could take a variable number of arguments, macros, arrays, non-local dynamic exits, fast arithmetic, the first good LISP compiler, and an emphasis on execution speed. By the end of the 1970's, MacLISP was in use at over 50 sites. InterLISP introduced many ideas into LISP programming environments and methodology. One of the InterLISP ideas that influenced Common LISP was an iteration construct that inspired the loop macro used both on the LISP Machines and in MacLISP, and now in Common LISP.
During the late 1970's, LISP Machine LISP began to expand towards a much fuller language. Sophisticated lambda lists, setf, multiple values, and structures like those in Common LISP are the results of early experimentation with programming styles by the LISP Machine group. These features were migrated to MacLISP. Around 1980, work was begun on a LISP to run on the Scientific Personal Integrated Computing Environment (SPICE) workstation. One of the goals of this project was to design a simpler dialect than LISP Machine LISP. The Macsyma group at MIT began a project during the late 1970's called the New Implementation of LISP (NIL) for the VAX, which was headed by White. One of the stated goals of the NIL project was to fix many of the historic, but annoying, problems with LISP while retaining significant compatibility with MacLISP. At about the same time, a research group at Stanford University and Lawrence Livermore National Laboratory began the design of a LISP to run on the S-1 Mark IIA supercomputer. S-1 LISP, never completely functional, was the test bed for adapting advanced compiler techniques to LISP implementation. Eventually the S-1 and NIL groups collaborated.
LISP Language StandardsThe first effort towards LISP standardization was made in 1969, when Anthony Hearn and Martin Griss at the University of Utah defined Standard LISP (a subset of LISP 1.5 and other dialects) to transport REDUCE, a symbolic algebra system. During the 1970's, the Utah group implemented first a retargetable optimizing compiler for Standard LISP, and then an extended implementation known as Portable Standard LISP (PSL). By the mid 1980's, PSL ran on about a dozen kinds of computers.PSL and Franz LISP (a MacLISP-like dialect for Unix machines) were the first examples of widely available LISP dialects on multiple hardware platforms. One of the most important developments in LISP occurred during the second half of the 1970's: Scheme. Scheme, designed by Gerald J. Sussman and Guy L. Steele Jr., is a simple dialect of LISP whose design brought to LISP some of the ideas from programming language semantics developed in the 1960's. Sussman was one of the prime innovators behind many other advances in LISP technology from the late 1960's through the 1970's. The major contributions of Scheme were lexical scoping, lexical closures, first-class continuations, and simplified syntax (no separation of value cells and function cells). Some of these contributions made a large impact on the design of Common LISP. In the late 1970's object-oriented programming concepts started to make a strong impact on LISP. At MIT, certain ideas from Smalltalk made their way into several widely used programming systems. Flavors, an object-oriented programming system with multiple inheritance, was developed at MIT for the LISP machine community by Howard Cannon and others. At Xerox, the experience with Smalltalk and Knowledge Representation Language (KRL) led to the development of LISP Object Oriented Programming System (LOOPS) and later Common LOOPS. These systems influenced the design of the Common LISP Object System (CLOS). CLOS was developed specifically for this standardization effort. In 1980 Symbolics and LMI were developing LISP Machine LISP; stock-hardware implementation groups were developing NIL, Franz LISP, and PSL; Xerox was developing Interlisp; and the SPICE project at CMU was developing a MacLISP-like dialect of LISP called SpiceLISP. In April 1981, after a DARPA-sponsored meeting concerning the splintered LISP community, Symbolics, the SPICE project, the NIL project, and the S-1 LISP project joined together to define Common LISP. Initially spearheaded by White and Gabriel, the driving force behind this grassroots effort was provided by Fahlman, Daniel Weinreb, David Moon, Steele, and Gabriel. Common LISP was designed as a description of a family of languages. The primary influences on Common LISP were LISP Machine LISP, MacLISP, NIL, S-1 LISP, Spice LISP, and Scheme. Common LISP: The Language is a description of that design. Its semantics were intentionally underspecified in places where it was felt that a tight specification would overly constrain Common LISP research and use. In 1986 X3J13 was formed as a technical working group to produce a draft for an ANSI Common LISP standard. Because of the acceptance of Common LISP, the goals of this group differed from those of the original designers. These new goals included stricter standardization for portability, an object-oriented programming system, a condition system, iteration facilities, and a way to handle large character sets. To accommodate those goals, a new LISP language specification was developed.
Why Study LISP?In general, all computer scientists, engineers, and technologists must understand the power of symbol manipulation as it is embodied in functional programming languages. There are relatively few functional programming languages, of these LISP is the most widely used. After you learn LISP, most of the other functional languages will be easy to learn.The ease of procedural abstraction combined with the strength of its macro-writing facilities makes LISP an ideal language for both, a) writing extensive libraries of general-purpose routines using user-tailored syntax, and b) writing complete new languages on top of LISP. LISP's simple syntax and weak typing, combined with friendly development environments, make it one of the most general and effective rapid prototyping languages.
LISP BasicsThe Interpreter. The GNU Common LISP development environment is interpretive. As illustrated by the example below, you start the interpreter by issuing the gcl command from the UNIX prompt. (Use the et105.ni.utoledo.edu server)et105:~/lisp$ gcl GCL (GNU Common Lisp) Version(2.2.2) Wed Sep 3 19:35:17 CDT 1997 Licensed under GNU Public Library License Contains Enhancements by W. Schelter >The > (greater than symbol) is the gcl prompt. In the examples below, this prompt will indicate that the GCL interpretive environment is being used. To exit the GCL interpreter and return to the UNIX prompt, issue the bye command at the gcl prompt. >(bye) et105:~/lisp$Do not forget the parentheses around the bye command. An error will result if you do. The example belwo illustrates this fact: >bye Error: The variable BYE is unbound. Fast links are on: do (si::use-fast-links nil) for debugging Error signalled by EVAL. Broken at EVAL. Type :H for Help. >>:q Top level. >The :q command is used to return to the Top level of the LISP interpreter from the debugging mode ... actomatically activated when the error occured. Simple Arithmetic. The example below illustrates the evaluation of some simple arithmetic forms (expressions). >(+ 2 77) 79 >(+ 3 (* 4 5)) 23 >(* 1 2 3 4) 24 >(+ (* 3 (+ 1 (- 4 2 (+ 3 4))))) -12 >Here are some built-in LISP functions. +, -, *, /, exp, expt, log, sqrt, sin, cos, tan, max, minThese two examples illustrate the use of built-in functions. Top level. >(sqrt 6) 2.4494897427831779 >(max 1 4 7 2 9 3 5 ) 9 >Division may result in a ratio result. >(/ 25 7) 25/7 >(float (/ 25 7)) 3.5714285714285716 >(round (float (/ 25 7))) 4 ; the nearest integer -0.42857142857142838 ; the remainder >The following example assigns the value 3 to the identifier items. >(setq items 3) 3 >items 3 >The following sequence illustrates the use of a simple conditional statement. Top level. >(setq p 1) 1 >(if p (+ 3 4) 6) 7 >(setq p 0) 0 >(if p (+ 3 4) 6) 7 >(setq p (eq 'a 'b)) NIL >(if p (+ 3 4) 6) 6 >Hello World. The example below shows a simple way to print the string Hello World in LISP. (print "Hello, world!") (princ "Hello, world!")This version first defines a function and then calls it.
(DEFUN HELLO-WORLD ()
(PRINT (LIST 'HELLO 'WORLD)))
(HELLO-WORLD)
Here is a version that is an infinite loop.
(LOOP (FORMAT T "~%Hello World"))If you run this version, use Ctrl+C to break out of the loop. Stored Programs. LISP programs can be stored in a file and run from within the interpreter. The example below illustrates the traditional Hello World program, written in LISP: ;;; HelloWorld.lsp ;;; ================================== ;;; ;;; === SIMPLE HELLO WORLD PROGRAM === ;;; ;;; ================================== ;;; ;;; This function returns the string ;;; Hello World that is in quotes. (DEFUN HELLO () "HELLO WORLD" )Use pico to create a program file for the program above and name it HelloWorld.lsp. To run this program, activate the LISP interpreter and then follow the example below: >(load "HelloWorld.lsp") Loading HelloWorld.lsp Finished loading HelloWorld.lsp T >(hello) "HELLO WORLD" >When loading, you may omit the .lsp extension from the filename. (defun what-is-your-name (name) (format NIL "Hello ~A!" name))
Program OrganizationUse whitespace appropriately. Use whitespace to separate semantically distinct code segments, but do not use too much whitespace. For example,
GOOD:
(defun foo (x y)
(let ((z (+ x y 10)))
(* z z)))
BAD:
(defun foo(x y)(let((z(+ x y 10)))(* z z)))
ALSO BAD:
(defun foo ( x y )
(let ( ( z (+ x y 10) ) )
( * z z )
)
)
Although the LISP reader and compiler does not care which style you use, most
experienced LISP programmers find the first example much easier to
read than the last two.
Comment your code. Use three semicolons in the left margin before the definition for major explanations. Use two semicolons that float with the code to explain the routine that follows. Two semicolons may also be used to explain the following line when the comment is too long for the single semicolon treatment. Use a single semicolon to the right of the code to explain a particular line with a short comment. The number of semicolons used roughly corresponds with the length of the comment. Put at least one blank line before and after top-level expressions. A function to convert from degrees Fahrenheit to degrees Celsuis. >(defun f-to-c (f) (float (- (/ (* (+ f 40) 5) 9) 40))) F-TO-C >(f-to-c 75) 23.888888888888889 >Going back the other way, a function to convert from degrees Celsuis to degrees Fahrenheit. >(defun c-to-f (c) (float (- (/ (* (+ c 40) 9) 5) 40))) C-TO-F >(c-to-f 23.9) 75.02000000000001 > Variables and IdentifiersThere are two kinds of variables in Common Lisp, in effect: ordinary variables and function names. There are some similarities between the two kinds, and in a few cases there are similar functions for dealing with them, for example boundp and fboundp. However, for the most part the two kinds of variables are used for very different purposes: one to name defined functions, macros, and special forms, and the other to name data objects.Use descriptive variable and function names. If it is not clear from the name of a function or variable what its purpose is, document it with a documentation string and a comment. In fact, even if the purpose is evident from the name, it is still worth documenting your code. Make the names of special (global) variables begin and end with an asterisk (*): (DEFVAR *GLOBAL-VARIABLE*)Some programmers will mark the beginning and end of an internal global variable with a percent (%) or a period (.). Use local variables in preference to global variables whenever possible. Do not use global variables in lieu of parameter passing. Global variables can be used in the following circumstances:
(defun power-of-two (n)
"Compute two raised to the exponent n,
where n must be a positive integer."
(declare (type integer n))
(do ((result 1 (* result 2))
(expon n (- expon 1)))
((>= 0 expon) result)))
Reserved WordsThere are no globaly reserved words in LISP. However, it does seem like a good idea to keep away from "self" and other words with special meanings in LISP.DeclarationsA type declaration is code written by a programmer that tells the computer that a given variable should only hold variables of a given type, or that a given function should have a given signature. It does two things:
>(defun discriminant (a b c)
(declare (number a b c))
"Compute the discriminant for a quadratic
equation. Given a, b, and c, the value
b^2-4*a*c is calculated. The quadratic
equation a*x^2+b*x+c=0 has real, multiple,
or complex roots depending on whether
this calculated value is positive, zero,
or negative, respectively."
(- (* b b) (* 4 a c)))
DISCRIMINANT
>(discriminant 1 2/3 -2)
76/9
>
Use the LISP interpreter,
to figure out how these functions and predicates work:
second, third, fourth,..., last, nthcar, nthcdr, butlast, nbutlast, reverse, caar, cddr, cadr, cdar, constantp, integerp Basic Data TypesThe two most important kinds of objects in LISP for you to know about are atoms and lists. These two kinds are mutually exclusive, with the exception of one special entity, the empty list, known as () or nil, which is both an atom and a list.
ATOMS LISTS a () john (a) 34 (a john 34 c3po) c3po ((john 34) a ((c3po)))Atoms and lists are both legal LISP expressions for the interpreter to read and evaluate. The rules for evaluating atoms and lists in LISP are very simple (one of the great beauties of the language). Recursion
>(defun fact (x) ;a recursive function
(if (> x 0)
(* x (fact (- x 1)))
1))
FACT
>(fact 6)
720
>
>(defun rpower-of-two (n)
(cond ((= n 0) 1)
(t (* 2 (rpower-of-two (- n 1))))))
RPOWER-OF-TWO
>(rpower-of-two 9)
512
(defun bottles (n)
"Prints the lyrics to '99 Bottles of Beer'"
(if (< n 1)
(format t "~%Time to go to the store.~%")
(progn (format t "~% ~a bottle~:p of beer on the wall." n)
(format t "~% ~a bottle~:p of beer." n)
(format t "~% Take one down, pass it around.")
(format t "~% ~a bottle~:p of beer on the wall.~%" (- n 1))
(bottles (- n 1)))))
Assignment StatementsActivate the LISP interpreter and try the following sequence of commands:
>(setq a (list "hello" "world"))
("hello" "world")
>(cadr a)
"world"
>(substring (cadr a) 2 4)
"rl"
>(setf (substring (cadr a) 2 4) "o")
"o"
>(cadr a)
"wood"
>a
("hello" "wood")
>
Numbers and ConstantsNumbers. An integer is a string of digits optionally preceded by + or -. A real number looks like an integer, except that it has a decimal point and optionally can be written in scientific notation. A rational looks like two integers with a / between them. LISP supports complex numbers, which are written #c(r i) (where r is the real part and i is the imaginary part). A number is any of the above. Here are some numbers:
5
17
-34
+6
3.1415
1.722e-15
#c(1.722e-15 0.75)
Constants. The defconstant special form is used for defining named constants.
The general syntax is;
defconstant name initial-value [documentation]A LISP convention is to put + plus signs around constants. Constants are declared via DEFCONSTANT, cannot be changed at run-time, and must be declared before they are used, since LISP replaces all the occurrences with the value. The following example illustrates the declaration of a constant: >(defconstant +half-pi+ (/ pi 2.0)) +HALF-PI+ >or (with an optional documentation string):
>(defconstant +half-pi+ (/ pi 2.0)
"pi/2, to avoid doing this division repeatedly")
+HALF-PI+
>
In either case, you can then use the constant.
Again, the convention is to make the names of constants begin and end with a + plus sign: >(defconstant +E+ 2.7182818) +E+ >(+ 3 +E+) 5.7182817999999997 >This approach helps distinguish constants from variables. Some people prefer to use macros to define constants, since this avoids the problem of coidentally trying to bind a symbol declared with defconstant. Boolean/Logical ComparisonsLISP uses the self-evaluating symbol nil to mean false. Anything other than nil means true. Unless we have a reason not to, we usually use the self-evaluating symbol t to stand for true.
>(setf thing 'circle r 4)
4
>(cond ((eq thing 'circle) (* pi r r))
((eq thing 'sphere) (* 4 pi r r)))
50.26548245743669
>
We could also make this a function:
>(defun area (thing r)
(cond ((eq thing 'circle) (* pi r r))
((eq thing 'sphere) (* 4 pi r r))))
AREA
>(area 'circle 2)
12.566370614359172
>
Printing
Top level. >(setf *global-variable* 33) 33 >*global-variable* 33 >(print *global-variable*) 33 33 >(princ *global-variable*) 33 33 >(prin1 *global-variable*) 33 33 >(pprint *global-variable*) 33 >
Directives
Directive Interpretation
--------- --------------
~% new line
~& fresh line
~| page break
~T tab stop
~< justification
~> terminate ~<
~C character
~( case conversion
~) terminate ~(
~D decimal integer
~B binary integer
~O octal integer
~X hexadecimal integer
~bR base-b integer
~R spell an integer
~P plural
~F floating point
~E scientific notation
~G ~F or ~E, depending upon magnitude
~$ monetary
~A legibly, without escapes
~S READably, with escapes
~~ ~
SortingLISP provides two primitives for sorting: sort and stable-sort.>(sort '(2 1 5 4 6) #'<) (1 2 4 5 6) >(setf a '(2 3 44 6 1 8 22 5)) (2 3 44 6 1 8 22 5) >(sort a #'<) (1 2 3 5 6 8 22 44) >(sort a #'>) (44 22 8 6 5 3 2) >The first argument to sort is a list; the second is a comparison function. The stable-sort function is exactly like sort, except that it guarantees that two equivalent elements appear in the sorted list in the same order that they appeared in the original list. Be careful: sort is allowed to destroy its argument, so if the original sequence is important to you, make a copy with the copy-list or copy-seq/ function.
Footnotes
| |||||||||||||||||||||||||||||||||||||
| |
| Added to the Web: November 1, 1999. Last updated: November 15, 1999. Web page design by Dan Solarek. |
![]() http://cset.sp.utoledo.edu/cset4250/ |