After completing this exercise, you will be able to:
format, print, and readdefvar, setq, and letif, cond, case)loop, do, dotimes, and dolistLisp (LISt Processor) is the second-oldest high-level programming language still in widespread use today, created by John McCarthy at MIT in 1958. Originally designed for artificial intelligence research, Lisp pioneered many programming concepts we now take for granted, including tree data structures, automatic storage management (garbage collection), dynamic typing, and the read-eval-print loop (REPL).
Common Lisp, standardized in 1984 and updated in 1994 (ANSI Common Lisp), is the modern dialect we will use. It supports multiple programming paradigms including:
Lisp source files typically use the .lisp or .lsp extension. We will use Steel Bank Common Lisp (SBCL), a high-performance Common Lisp compiler.
Create a file called hello.lisp with the following content:
; My first Lisp program
(format t "Hello, World!~%")
Open a terminal and navigate to the directory containing your file. Run the program with:
sbcl --script hello.lisp
You should see Hello, World! printed to the console.
sbcl to enter the REPL (Read-Eval-Print Loop), where you can type and evaluate expressions directly.
| Code Element | Explanation |
|---|---|
; |
Single-line comment (everything after is ignored) |
( ) |
Parentheses delimit all expressions in Lisp |
format |
The formatted output function |
t |
Output destination: t means standard output (terminal) |
"Hello, World!~%" |
Format string; ~% is the newline directive |
(function arg1 arg2 ...)
All Lisp code is written as S-expressions (symbolic expressions). There are two types:
; Atoms
42 ; a number
"hello" ; a string
x ; a symbol
nil ; the empty list / false value
t ; the boolean true value
; Lists
(1 2 3) ; a list of three numbers
(+ 2 3) ; a list that is also code: adds 2 and 3
(a (b c) d) ; a nested list
Lisp uses prefix notation (also called Polish notation): the operator comes first, followed by its operands.
; Mathematical operations
(+ 2 3) ; → 5 (same as 2 + 3)
(- 10 4) ; → 6 (same as 10 - 4)
(* 6 7) ; → 42 (same as 6 * 7)
(/ 20 4) ; → 5 (same as 20 / 4)
; Operators can take multiple arguments
(+ 1 2 3 4) ; → 10 (same as 1 + 2 + 3 + 4)
(* 2 3 4) ; → 24 (same as 2 * 3 * 4)
; Nested expressions
(+ (* 2 3) (- 10 5)) ; → 11 (same as (2*3) + (10-5))
Common Lisp has a rich type system. Here are the fundamental types you'll use most often:
| Type | Description | Examples |
|---|---|---|
integer |
Whole numbers (arbitrary precision) | 42, -17, 1000000000000 |
ratio |
Exact fractions | 1/3, 22/7, -5/2 |
float |
Floating-point numbers | 3.14, 2.5e10, -0.001 |
complex |
Complex numbers | #C(3 4) represents 3+4i |
(/ 5 3) returns 5/3, not 1.
| Type | Description | Examples |
|---|---|---|
string |
Sequence of characters | "Hello", "Lisp is fun!" |
character |
Single character | #\A, #\Space, #\Newline |
symbol |
Named identifier | x, my-variable, + |
boolean |
True or false | t (true), nil (false) |
list |
Ordered collection | '(1 2 3), '(a b c) |
nil represents both the empty list () and the boolean false value. Everything else is considered "true" in boolean contexts.
Global variables are conventionally named with asterisks (called "earmuffs"):
; Define global variables
(defvar *name* "Alice") ; only sets if undefined
(defparameter *pi* 3.14159) ; always sets the value
(defvar *count* 0)
(defvar *enrolled* t)
; Use the variables
(format t "Name: ~a~%" *name*)
(format t "Pi: ~a~%" *pi*)
Use let to create local variables with limited scope:
(let ((x 10)
(y 20)
(name "Bob"))
(format t "x = ~a, y = ~a~%" x y)
(format t "Sum = ~a~%" (+ x y))
(format t "Name: ~a~%" name))
; x, y, and name are not accessible outside the let
(defvar *counter* 0)
(setq *counter* 10) ; set *counter* to 10
(setq *counter* (+ *counter* 1)) ; increment by 1
; setf is more general and works with any "place"
(setf *counter* 20) ; same as setq for simple variables
; variables.lisp
(defvar *age* 25)
(defvar *gpa* 3.85)
(defvar *name* "Alice")
(defvar *enrolled* t)
(format t "Name: ~a~%" *name*)
(format t "Age: ~a~%" *age*)
(format t "GPA: ~a~%" *gpa*)
(format t "Enrolled: ~a~%" *enrolled*)
; Using let for local scope
(let ((count 100)
(pi-approx 3.14159)
(greeting "Hi"))
(format t "~%Local variables:~%")
(format t "Count: ~a~%" count)
(format t "Pi: ~a~%" pi-approx)
(format t "Greeting: ~a~%" greeting))
Common Lisp provides several functions for output:
| Function | Description |
|---|---|
(format t ...) |
Formatted output (most flexible) |
(print x) |
Prints with newline before and space after (readable form) |
(princ x) |
Prints without extra formatting (human-readable) |
(write-line str) |
Prints string with newline |
(terpri) |
Prints a newline only |
format is the most powerful output function. The first argument is the destination (t for standard output, nil to return a string).
; output.lisp
(let ((x 42)
(price 19.99)
(item "widget"))
; Basic output
(format t "The answer is ~a~%" x)
; Multiple values
(format t "Item: ~a, Price: $~a~%" item price)
; Using princ (no newline)
(princ "Processing")
(princ "...")
(format t " Done!~%")
; Formatted numbers
(format t "The ~a costs $~,2f~%" item price)
(format t "Hex: ~x, Padded: ~5,'0d~%" x x))
| Directive | Description | Example Output |
|---|---|---|
~a |
Aesthetic (human-readable) | hello |
~s |
Standard (machine-readable, with quotes) | "hello" |
~d |
Decimal integer | 42 |
~f |
Floating-point | 3.141593 |
~,2f |
Float with 2 decimal places | 3.14 |
~x |
Hexadecimal | 2a |
~% |
Newline | (line break) |
~~ |
Literal tilde | ~ |
~5,'0d |
Zero-padded to 5 digits | 00042 |
Reading input in Common Lisp uses read for Lisp data or read-line for strings:
; input.lisp
; Reading a string
(format t "Enter your name: ")
(force-output) ; ensure prompt appears before input
(defvar *name* (read-line))
; Reading a number - method 1: parse from string
(format t "Enter your age: ")
(force-output)
(defvar *age* (parse-integer (read-line)))
; Reading a number - method 2: use read for Lisp data
(format t "Enter your GPA: ")
(force-output)
(defvar *gpa* (read))
(format t "Hello, ~a! Age: ~a, GPA: ~,2f~%" *name* *age* *gpa*)
read evaluates input as Lisp data, so numbers are read directly. However, for strings, users would need to type quotes. Use read-line for string input from users, and parse-integer or read-from-string to convert to numbers.
| Function | Description | Use Case |
|---|---|---|
(read-line) |
Reads one line as a string | User text input |
(read) |
Reads and parses one Lisp expression | Numbers, symbols, lists |
(parse-integer str) |
Converts string to integer | Integer from text |
(read-from-string str) |
Parses Lisp expression from string | Float from text |
(force-output) |
Flushes output buffer | Ensure prompt displays |
| Operator | Description | Example | Result |
|---|---|---|---|
+ |
Addition | (+ 10 3) |
13 |
- |
Subtraction | (- 10 3) |
7 |
* |
Multiplication | (* 10 3) |
30 |
/ |
Division | (/ 10 3) |
10/3 (ratio) |
mod |
Modulus (remainder) | (mod 10 3) |
1 |
rem |
Remainder | (rem 10 3) |
1 |
expt |
Exponentiation | (expt 2 10) |
1024 |
sqrt |
Square root | (sqrt 16) |
4.0 |
abs |
Absolute value | (abs -5) |
5 |
; division.lisp
(format t "Regular division: ~a~%" (/ 7 2)) ; → 7/2 (ratio)
(format t "Float division: ~a~%" (/ 7.0 2)) ; → 3.5
(format t "Floor division: ~a~%" (floor 7 2)) ; → 3
(format t "Ceiling division: ~a~%" (ceiling 7 2)) ; → 4
(format t "Truncate: ~a~%" (truncate 7 2)) ; → 3
(format t "Round: ~a~%" (round 7 2)) ; → 4
(defvar *count* 5)
(incf *count*) ; *count* is now 6 (increment by 1)
(incf *count* 3) ; *count* is now 9 (increment by 3)
(decf *count*) ; *count* is now 8 (decrement by 1)
(decf *count* 2) ; *count* is now 6 (decrement by 2)
Basic conditional with one then-clause and one optional else-clause:
; Syntax: (if test then-form else-form)
(if (> x 0)
(format t "Positive~%")
(format t "Not positive~%"))
; Without else clause
(if (= x 0)
(format t "Zero~%"))
if only allows ONE form in the then-branch and ONE in the else-branch. To execute multiple forms, wrap them in progn.
; Multiple statements using progn
(if (> score 90)
(progn
(format t "Excellent!~%")
(format t "Grade: A~%"))
(format t "Keep trying!~%"))
Cleaner alternatives when you only need one branch:
; when - executes body if test is true
(when (> score 90)
(format t "Excellent!~%")
(format t "Grade: A~%"))
; unless - executes body if test is false
(unless (= divisor 0)
(format t "Result: ~a~%" (/ dividend divisor)))
For multiple conditions (like if-else if-else chains):
; grader.lisp
(format t "Enter your score (0-100): ")
(force-output)
(let ((score (parse-integer (read-line))))
(cond
((>= score 90) (format t "Grade: A~%"))
((>= score 80) (format t "Grade: B~%"))
((>= score 70) (format t "Grade: C~%"))
((>= score 60) (format t "Grade: D~%"))
(t (format t "Grade: F~%")))) ; t is the default/else case
| Operator | Meaning | Example |
|---|---|---|
= |
Numeric equality | (= x 5) |
/= |
Numeric inequality | (/= x 5) |
< |
Less than | (< x 5) |
> |
Greater than | (> x 5) |
<= |
Less than or equal | (<= x 5) |
>= |
Greater than or equal | (>= x 5) |
equal |
General equality (works with strings) | (equal str "yes") |
string= |
String equality | (string= str "yes") |
| Operator | Meaning | Example |
|---|---|---|
and |
Logical AND | (and (> x 0) (< x 100)) |
or |
Logical OR | (or (= x 0) (= x 1)) |
not |
Logical NOT | (not found) |
; logical.lisp
(format t "Enter your age: ")
(force-output)
(let ((age (parse-integer (read-line))))
(format t "Do you have a license? (yes/no): ")
(force-output)
(let* ((response (read-line))
(has-license (string= response "yes")))
(cond
((and (>= age 16) has-license)
(format t "You can drive.~%"))
((and (>= age 16) (not has-license))
(format t "You're old enough but need a license.~%"))
(t
(format t "You're too young to drive.~%")))))
For comparing a value against multiple constants:
; days.lisp
(format t "Enter a day number (1-7): ")
(force-output)
(let ((day (parse-integer (read-line))))
(case day
(1 (format t "Monday~%"))
(2 (format t "Tuesday~%"))
(3 (format t "Wednesday~%"))
(4 (format t "Thursday~%"))
(5 (format t "Friday~%"))
((6 7) (format t "Weekend!~%")) ; multiple values
(otherwise (format t "Invalid day number~%"))))
((6 7) ...). Use otherwise (or t) for the default case.
The simplest loop repeats forever until return is called:
; simple-loop.lisp
(let ((count 1))
(loop
(when (> count 5)
(return))
(format t "Count: ~a~%" count)
(incf count)))
The extended loop provides a rich, English-like syntax:
; Counting from 1 to 5
(loop for i from 1 to 5
do (format t "Count: ~a~%" i))
; Counting down
(format t "~%Countdown:~%")
(loop for i from 10 downto 0
do (format t "~a~%" i))
; Step by 2
(format t "~%Even numbers:~%")
(loop for i from 2 to 10 by 2
do (format t "~a~%" i))
; While condition
(let ((x 1))
(loop while (<= x 5)
do (format t "x = ~a~%" x)
(incf x)))
; Collecting results into a list
(loop for i from 1 to 5
collect (* i i)) ; → (1 4 9 16 25)
Iterate a specific number of times (counter starts at 0):
; dotimes.lisp
(format t "Counting 0 to 4:~%")
(dotimes (i 5)
(format t "Iteration ~a~%" i))
; With return value
(dotimes (i 5 "Done!")
(format t "~a " i)) ; prints 0 1 2 3 4, returns "Done!"
Iterate over elements of a list:
; dolist.lisp
(dolist (name '("Alice" "Bob" "Charlie"))
(format t "Hello, ~a!~%" name))
(dolist (n '(1 2 3 4 5))
(format t "Square of ~a is ~a~%" n (* n n)))
The most general iteration construct:
; Syntax: (do ((var init step) ...) (end-test result) body...)
; Count from 1 to 5
(do ((i 1 (+ i 1))) ; var, init, step
((> i 5)) ; end-test
(format t "Count: ~a~%" i))
; Multiple variables
(do ((i 0 (+ i 1))
(sum 0 (+ sum i)))
((> i 10) sum) ; returns sum when done
(format t "i=~a, sum=~a~%" i sum))
; multiplication-table.lisp
(format t "Multiplication Table (1-5):~%~%")
; Print header row
(format t " ")
(loop for col from 1 to 5
do (format t "~4d" col))
(format t "~% --------------------~%")
; Print table rows
(loop for row from 1 to 5
do (format t "~2d |" row)
(loop for col from 1 to 5
do (format t "~4d" (* row col)))
(format t "~%"))
; break-equivalent: return exits the immediately enclosing block
(format t "Finding first multiple of 7:~%")
(loop for i from 1 to 100
do (when (= (mod i 7) 0)
(format t "Found: ~a~%" i)
(return)))
; continue-equivalent: use conditional to skip
(format t "~%Numbers 1-10, skipping multiples of 3:~%")
(loop for i from 1 to 10
do (unless (= (mod i 3) 0)
(format t "~a~%" i)))
Functions are defined using defun:
; Define a simple function
(defun square (x)
"Returns the square of x." ; optional docstring
(* x x))
; Call the function
(format t "5 squared = ~a~%" (square 5))
; Function with multiple parameters
(defun greet (name greeting)
(format t "~a, ~a!~%" greeting name))
(greet "Alice" "Hello")
; Function that returns a value
(defun celsius-to-fahrenheit (c)
(+ (* c 9/5) 32))
(format t "20°C = ~a°F~%" (celsius-to-fahrenheit 20))
This program demonstrates input, output, control structures, and loops working together:
; temperature-converter.lisp
(defun celsius-to-fahrenheit (c)
"Convert Celsius to Fahrenheit."
(+ (* c 9.0 (/ 1 5.0)) 32.0))
(defun fahrenheit-to-celsius (f)
"Convert Fahrenheit to Celsius."
(* (- f 32.0) 5.0 (/ 1 9.0)))
(defun main ()
(format t "=== Temperature Converter ===~%")
(format t "1. Celsius to Fahrenheit~%")
(format t "2. Fahrenheit to Celsius~%")
(format t "3. Quit~%")
(loop
(format t "~%Enter choice (1-3): ")
(force-output)
(let ((choice (parse-integer (read-line) :junk-allowed t)))
(case choice
(1
(format t "Enter temperature in Celsius: ")
(force-output)
(let* ((celsius (read))
(fahrenheit (celsius-to-fahrenheit celsius)))
(format t "~,2f°C = ~,2f°F~%" celsius fahrenheit)))
(2
(format t "Enter temperature in Fahrenheit: ")
(force-output)
(let* ((fahrenheit (read))
(celsius (fahrenheit-to-celsius fahrenheit)))
(format t "~,2f°F = ~,2f°C~%" fahrenheit celsius)))
(3
(format t "Goodbye!~%")
(return))
(otherwise
(format t "Invalid choice. Please enter 1, 2, or 3.~%"))))))
; Run the program
(main)
| Task | Common Lisp Syntax |
|---|---|
| Print with newline | (format t "text ~a~%" variable) |
| Print without newline | (format t "text") or (princ "text") |
| Formatted print | (format t "Value: ~d~%" x) |
| Read string | (read-line) |
| Read integer | (parse-integer (read-line)) |
| Read any number | (read) |
| Define global variable | (defvar *name* value) |
| Define local variable | (let ((var value)) ...) |
| Assign to variable | (setq var value) |
| Define function | (defun name (args) body) |
| Conditional | (if test then else) |
| Multiple conditions | (cond (test1 action1) (test2 action2) ...) |
| Loop n times | (dotimes (i n) ...) |
| Loop with range | (loop for i from a to b do ...) |
| Run program | sbcl --script filename.lisp |
Write a Common Lisp program that analyzes prime numbers. Your program should:
(is-prime n) that returns t or nil(mod n divisor) to check if n is divisible by divisor(loop for i from 2 to n count (is-prime i)) to count primesstring=: (string= response "y")(force-output) after prompts to ensure they displaySave your program as prime-analyzer.lisp and run with:
sbcl --script prime-analyzer.lisp