An Informal Introduction to Common Lisp
This informal introduction is meant to be an example driven first introduction to interacting with a Common Lisp Read-Eval-Print-Loop (REPL). Please start a REPL in your terminal or IDE of choice and follow along. If you installed SBCL in the last tutorial, simply run $ sbcl
in your terminal to start a REPL.
In the following examples, input and output are distinguished by the presence or absence of prompts. To repeat the example, you must type everything after the prompt, when the prompt appears; lines that do not begin with a prompt are output from the Lisp system. In Common Lisp, the standard interactive prompt is often >
(though this can vary between implementations). We will use >
here.
You can copy and paste the input lines into your Lisp environment (often called a REPL, for Read-Eval-Print Loop).
Many of the examples in this manual, even those entered at the interactive prompt, include comments. Comments in Common Lisp start with a semicolon ;
and extend to the end of the line. A single semicolon is typically used for short comments on the same line as code. Two semicolons ;;
are used for comments that form a paragraph on their own and describe a section of code. Three semicolons ;;;
are used for file-level comments introducing a file or a major section of code within a file. A comment may appear at the start of a line or following whitespace or code, but not within a string literal. A semicolon within a string literal is just a semicolon.
Since comments are to clarify code and are not interpreted by Lisp, they may be omitted when typing in examples.
Some examples:
; this is the first comment
(defvar spam 1) ; and this is the second comment
;; ... and now a third!
(defvar text "; This is not a comment because it's inside quotes.")
As you may have guessed the form defvar
above defines a variable. We'll delve into this a bit later.
1 Using Common Lisp as a Calculator
Let’s try some simple Common Lisp commands. Start your Lisp environment (often a REPL) and wait for the prompt, often >
.
1.1 Numbers
The Lisp environment acts as a simple calculator: you can type an expression at it and it will print the value. Expression syntax uses prefix notation (also known as Polish notation): the operator comes before the operands. Parentheses ()
are used for grouping and function calls. For example:
(+ 2 2)
Output:
4
(- 50 (* 5 6))
Output:
20
(/ (- 50 (* 5 6)) 4)
Output:
5
(/ 8 5) ; division by default yields a ratio or a float if appropriate
Output (depending on the implementation and settings):
8/5 ; Ratio
or
1.6 ; Float
Common Lisp has several numeric types: integers (e.g., 2, 4, 20), ratios (e.g. 8/5), and floating-point numbers (e.g., 1.6, 5.0).
To get an integer quotient (floor division) you can use floor
:
(floor 17 3)
Output:
5
floor
actually returns two values: the quotient and the remainder. To get just the remainder, use mod
:
(mod 17 3)
Output:
2
To get both the quotient and remainder use multiple-value-bind
(multiple-value-bind (quotient remainder) (floor 17 3)
(format t "Quotient: ~a, Remainder: ~a~%" quotient remainder))
Output:
Quotient: 5, Remainder: 2
(+ (* 5 3) 2) ;floored quotient * divisor + remainder
Output:
17
In Common Lisp, you can use expt
to calculate powers:
(expt 5 2) ; 5 squared
Output:
25
(expt 2 7) ; 2 to the power of 7
Output:
128
The special operator setf
is used to assign a value to a variable:
(setf width 20)
(setf height (* 5 9))
(* width height)
Output:
900
If a variable is not defined (assigned a value), trying to use it will signal an unbound-variable error:
n ; try to access an undefined variable
This will signal an error similar to:
The variable N is unbound.
Common Lisp has full support for floating-point numbers. Operations with mixed numeric types generally result in a floating-point number if one of the operands is a float:
(- (* 4 3.75) 1)
Output:
14.0
In the REPL, the value of the last evaluated expression is available through the special variable *
. This is similar to _
in Python's interactive mode.
(setf tax (/ 12.5 100))
(setf price 100.50)
(* price tax)
Output:
12.5625
(+ price *)
Output:
113.0625
(round * 2) ; Rounding to two decimal places
Output:
113.06
You should treat *
as read-only. Avoid assigning to it explicitly.
Common Lisp supports other numeric types, including complex numbers. The imaginary unit is represented as #C(0 1)
or 0+1i
(* #C(0 1) #C(0 1)) ; i * i = -1
Output:
-1
or
#C(-1 0)
1.2. Text
Common Lisp can manipulate text (represented by the type string
) as well as numbers. This includes characters “!”, words “rabbit”, names “Paris”, sentences “Got your back.”, etc. “Yay! :)”. They are enclosed in double quotes "..."
.
"spam eggs"
Output:
"spam eggs"
"Paris rabbit got your back :)! Yay!"
Output:
"Paris rabbit got your back :)! Yay!"
"1975" ; digits and numerals enclosed in quotes are also strings
Output:
"1975"
Single quotes '
are not string delimiters.
"doesn't"
Output:
"doesn't"
To include a double quote within a string, you need to escape it using a backslash \
:
"\"Yes,\" they said."
Output:
"\"Yes,\" they said."
In the Lisp REPL, the string definition and output string are the same. The print
function produces a more readable output, but for strings, it doesn't remove the quotes unless you use princ
.
Special characters like \n
in other languages are not interpreted as special characters in Lisp. (unless you use the interop library for this). The format
directive has its own special characters, which will be discussed later on in this tutorial. For now, note that ~%
is the directive for a new line. The t
in the example below means to print to the standrd output.
CL-USER> (format t "First line.~%Second line.")
First line.
Second line.
NIL
Common Lisp doesn't have "raw strings" in the same way as Python. Backslashes are always interpreted as escape characters unless they are themselves escaped (e.g., \\
).
String literals can span multiple lines without the need for any special syntax:
(setf long-string "Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to")
(print long-string)
Output:
"Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to"
Strings can be concatenated (joined together) using concatenate
:
(concatenate 'string "un" "un" "un" "ium")
Output:
"unununium"
There is no automatic concatenation of adjacent string literals in Common Lisp. You must always use concatenate
.
(concatenate 'string "Li" "sp")
Output:
"Lisp"
(setf prefix "Li")
(concatenate 'string prefix "sp")
Output:
"Lisp"
Strings can be accessed by index using aref
. The first character has index 0:
(setf word "Lisp")
(aref word 0) ; character in position 0
Output:
#\L
(aref word 2) ; character in position 5
Output:
#\s
Indices can't be negative in Common Lisp's aref
.
To get a substring (slicing), use subseq
:
(subseq word 0 2) ; characters from position 0 (included) to 2 (excluded)
Output:
"Li"
(subseq word 2 3) ; characters from position 2 (included) to 3 (excluded)
Output:
"s"
Slice indices have useful defaults; an omitted first index defaults to zero, and an omitted second index defaults to the length of the string:
(subseq word 0) ; characters from the beginning to the end
Output:
"Lisp"
(subseq word 2) ; characters from position 4 (included) to the end
Output:
"sp"
Common Lisp strings are mutable. You can change individual characters using (setf (aref string index) new-character)
:
(setf word "List")
(setf (aref word 3) #\p)
word
Output:
"Lisp"
The function length
returns the length of a string:
(setf s "supercalifragilisticexpialidocious")
(length s)
Output:
34
1.3. Lists
Common Lisp uses lists as its primary compound data type to group together other values. Lists are written as a sequence of space-separated values (items) enclosed in parentheses. Lists can contain items of different types.
(setf squares (list 1 4 9 16 25))
Evaluating squares
in the REPL:
squares
Output:
(1 4 9 16 25)
Like strings (and other sequence types), lists can be accessed by index using elt
:
(elt squares 0) ; indexing returns the item
Output:
1
In addition nth
can be used to access the nth item in a 0-indexed list.
CL-USER> (nth 2 (list 1 2 3 4))
3 (2 bits, #x3, #o3, #b11)
Common Lisp does not support negative indexing with nth
or elt
. To access elements from the end, use a calculated index:
(nth (- (length squares) 1) squares) ; last element (equivalent to squares[-1] in Python)
Output:
25
or
(car (last squares))
Output:
25
To get a sublist (slicing), use subseq
:
(subseq squares 2 5) ; slicing returns a new list (equivalent to squares[2:5] in Python)
Output:
(9 16 25)
Lists can be concatenated using append
:
(append squares (list 36 49 64 81 100))
Output:
(1 4 9 16 25 36 49 64 81 100)
Lists in Common Lisp are mutable. You can change their content using setf
with elt
:
(setf cubes (list 1 8 27 65 125)) ; something's wrong here
(expt 4 3) ; the cube of 4 is 64, not 65!
Output:
64
(setf (elt cubes 3) 64) ; replace the wrong value
cubes
Output:
(1 8 27 64 125)
You can add new items to a list using push
or append
. push
adds to the beginning, while append
creates a new list. For adding to the end, nconc
is often more efficient than append
:
(setf cubes (nconc cubes (list 216))) ; add the cube of 6
(setf cubes (nconc cubes (list (expt 7 3)))) ; and the cube of 7
cubes
Output:
(1 8 27 64 125 216 343)
Simple assignment in Common Lisp, like in Python, does not copy data. When you assign a list to a variable, the variable refers to the existing list:
(setf rgb (list "Red" "Green" "Blue"))
(setf rgba rgb)
(eq rgb rgba) ; they reference the same object (similar to id() in Python)
Output:
T
(nconc rgba (list "Alph"))
rgb
Output:
("Red" "Green" "Blue" "Alph")
To create a copy of a list, use copy-list
:
(setf correct-rgba (copy-list rgba))
(setf (elt correct-rgba (- (length correct-rgba) 1)) "Alpha")
correct-rgba
Output:
("Red" "Green" "Blue" "Alpha")
rgba
Output:
("Red" "Green" "Blue" "Alph")
Assignment to slices (using setf
with subseq
) is also possible:
(setf letters (list 'a 'b 'c 'd 'e 'f 'g))
letters
Output:
(A B C D E F G)
(setf (subseq letters 2 5) (list 'C 'D 'E))
letters
Output:
(A B C D E F G)
To remove elements, you can use setf
with subseq
and an empty list, or use delete
:
(setf (subseq letters 2 5) nil)
letters
Output:
(A B F G)
To clear the list:
(setf letters nil)
letters
Output:
NIL
The function length
also applies to lists:
(setf letters (list 'a 'b 'c 'd))
(length letters)
Output:
4
It is possible to nest lists:
(setf a (list 'a 'b 'c))
(setf n (list 1 2 3))
(setf x (list a n))
x
Output:
((A B C) (1 2 3))
(elt x 0)
Output:
(A B C)
(elt (elt x 0) 1)
Output:
B
1.4. First Steps Towards Programming
Of course, we can use Common Lisp for more complicated tasks than adding two and two together. For instance, we can write an initial sub-sequence of the Fibonacci series as follows:
;; Fibonacci series:
;; the sum of two elements defines the next
(let ((a 0) (b 1)) ; Multiple assignment using LET
(loop
(when (> a 10) (return)) ; Exit the loop when a >= 10
(print a)
(rotatef a b (+ a b)))) ; Simultaneous assignment using ROTATEF
Output:
0
1
1
2
3
5
8
This example introduces several new features.
-
The first line uses
let
with a binding list((a 0) (b 1))
to introduce local variablesa
andb
and simultaneously initialize them to 0 and 1. This is Common Lisp's equivalent of multiple assignment. The last line usesrotatef
which is the idiomatic way to swap variable values and perform simultaneous assignment in Common Lisp.rotatef
rotates the values of the given variables.(rotatef a b (+ a b))
is equivalent to the Pythona, b = b, a+b
. -
The
loop
macro introduces an infinite loop. Thewhen
form provides a conditional exit from the loop.(when (> a 10) (return))
is equivalent toif a > 10: break
in Python. The loop continues as long as the condition(> a 10)
is false (i.e.,a
is less than or equal to 10). The standard comparison operators are similar to C and Python:<
(less than),>
(greater than),=
(equal to),<=
(less than or equal to),>=
(greater than or equal to), and/=
(not equal to). Note that=
is for numeric equality,eql
is for general object equality (and is what is usually wanted) andeq
is for object identity. -
Indentation is not syntactically significant in Common Lisp, unlike Python. However, it is extremely important for readability. The code within the
let
andloop
forms is indented to show the structure. Common Lisp code is typically formatted using consistent indentation. -
The
print
function writes the value of its argument(s). It prints a newline after each argument by default. To print without a newline, you can useprinc
orwrite-string
to a stream. To format output,format
is the standard way. It is much more powerful than Python'sprint
.
Example using format
:
(let ((i (* 256 256)))
(format t "The value of i is ~d~%" i)) ; ~d is for decimal integer, ~% is for newline
Output:
The value of i is 65536
To avoid the newline after the output, or end the output with a different string, you can use format
with different directives. For example, to separate numbers with commas:
(let ((a 0) (b 1))
(loop
(when (> a 1000) (return))
(format t "~d," a) ; Print a followed by a comma, no newline
(rotatef a b (+ a b))))
(terpri) ; Print a final newline
Output (the last comma will remain, a more sophisticated format string could avoid that):
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,
A more elegant way to avoid the trailing comma is to use loop for
and format
with conditional printing:
(let ((fib_numbers (loop for a = 0 then b
and b = 1 then (+ a b)
while (< a 1000)
collect a)))
(format t "~{~A~^, ~}" fib_numbers)) ; Print each element, and a comma unless it's the last one.
(terpri)
Output:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987
NIL
The format
form is very powerful and it deserves a separate tutorial on it's own; however, here's a short intro to get you started:
1.5. The format
directive
Let's explore the basics of the Common Lisp format
directive. format
is a powerful tool for creating formatted output. It takes a destination (usually t
for standard output, or nil
to return a string), a format string, and any arguments to be formatted.
1. Basic String Output
The simplest use is just printing a string:
(format t "Hello, world!")
Output:
Hello, world!
2. Inserting Values
The ~a
directive inserts an argument using princ
(human-readable representation):
(let ((name "Alice"))
(format t "Hello, ~a!" name))
Output:
Hello, Alice!
3. Newlines
~%
inserts a newline character:
(format t "First line.~%Second line.")
Output:
First line.
Second line.
4. Formatting Integers
~d
formats an integer in decimal:
(format t "The number is ~d." 42)
Output:
The number is 42.
~x
formats an integer in hexadecimal:
(format t "The hexadecimal value is ~x." 255)
Output:
The hexadecimal value is ff.
5. Formatting Floating-Point Numbers
~f
formats a floating-point number:
(format t "The value is ~f." 3.14159)
Output:
The value is 3.14159.
You can control the number of decimal places:
(format t "The value is ~,2f." 3.14159) ; 2 decimal places
Output:
The value is 3.14
6. Conditional Text
~[...~;...~]
allows for conditional output. The first clause is used if the argument is nil
, the second if it is not:
(format t "The value is ~[nil~;not nil~]." nil)
(format t "The value is ~[nil~;not nil~]." 1)
Output:
The value is nil.
The value is not nil.
7. Iterating over Lists (and avoiding trailing separators)
~{...~}
iterates over a list. Combined with ~^
, you can avoid trailing separators:
(format t "The numbers are: ~{~a~^, ~}." '(1 2 3 4))
Output:
The numbers are: 1, 2, 3, 4.
8. Returning a String
To get the formatted output as a string instead of printing it, use nil
as the first argument:
(let ((formatted-string (format nil "The answer is ~d." 42)))
(print formatted-string))
Output:
"The answer is 42."
These examples cover the most commonly used format
directives. format
is much more powerful than this short introduction shows, but these basics will get you started. For more details, consult the Common Lisp Technical Reference's Section on Formatted Output (22.3).