Introduction to Common Lisp #
What is Common Lisp? #
Common Lisp (often abbreviated as CL) is a high-level, general-purpose programming language with a long and rich history. It’s known for its powerful macro system, dynamic typing, and extensive standard library. Unlike many modern languages that prioritize specific paradigms (like object-oriented programming), Common Lisp is multi-paradigm, allowing you to write code in a functional, object-oriented, imperative, or even metaprogramming style, often within the same project. This flexibility makes it suitable for a wide range of tasks, from web development and artificial intelligence to systems programming and scripting. Its homoiconicity (code is data) allows for incredibly powerful metaprogramming capabilities.
Why Learn Common Lisp? #
Learning Common Lisp offers several advantages:
- Expressive Power: The language’s features, including macros and its flexible type system, enable you to express complex ideas concisely and elegantly.
- Readability: Well-written Common Lisp code is generally considered very readable, making it easier to maintain and collaborate on projects.
- Extensibility: The powerful macro system allows you to extend the language itself to suit your needs, creating domain-specific languages (DSLs) within Common Lisp.
- Large and Active Community: While not as large as some other languages, the Common Lisp community is active, helpful, and dedicated to maintaining and improving the language.
- Long-Term Stability: Common Lisp has a mature standard (ANSI Common Lisp), ensuring code written today will likely remain compatible with future implementations.
- Excellent REPL (Read-Eval-Print Loop): The REPL is an interactive environment that allows for rapid prototyping, experimentation, and debugging.
Setting up your environment #
Setting up your Common Lisp environment is relatively straightforward. The process generally involves these steps:
- Download a Lisp Implementation: Choose an implementation (see the next section for recommendations).
- Install the Implementation: Follow the installation instructions provided by your chosen implementation. This typically involves downloading a binary or compiling source code, depending on your operating system.
- Test the Installation: Once installed, run the Lisp implementation from your terminal or command prompt. You should see a REPL prompt where you can enter and evaluate Lisp code.
- (Optional) Install a Text Editor or IDE: While you can technically use any text editor, using a dedicated editor or IDE with Lisp support will significantly improve your development experience. Popular choices include Emacs (with SLIME), VS Code (with various extensions), and Sublime Text (with plugins).
Choosing a Lisp implementation (SBCL, CCL, ECL) #
Several excellent Common Lisp implementations are available. Here are three popular choices:
SBCL (Steel Bank Common Lisp): A widely used, highly optimized, and mature implementation known for its performance and stability. It’s a good general-purpose choice for most developers.
CCL (Clozure Common Lisp): Another robust implementation that excels in performance, particularly on systems with advanced hardware capabilities. It’s a solid alternative to SBCL.
ECL (Embeddable Common Lisp): Designed for embeddability in other applications. If you need to integrate Common Lisp into a larger system, ECL is a strong contender. It’s also a good choice for running Lisp on resource-constrained environments.
The best choice depends on your specific needs and preferences. For beginners, SBCL is often recommended due to its ease of use and widespread community support. All three implementations are excellent and provide a robust foundation for learning and developing in Common Lisp.
Basic Syntax and Data Types #
REPL interaction #
The REPL (Read-Eval-Print Loop) is your primary interface for interacting with Common Lisp. You type Lisp expressions, the REPL evaluates them, and prints the result.
Entering Expressions: Type a Lisp expression and press Enter. Expressions generally follow a prefix notation (operator first, then operands). For example:
(+ 1 2)
adds 1 and 2.Evaluating Expressions: The REPL evaluates the expression and displays the result.
(+ 1 2)
would produce3
.Reading Errors: If you type an invalid expression, the REPL will display an error message.
Using the REPL for experimentation: The REPL is an invaluable tool for experimenting with code, testing functions, and exploring the language interactively.
Numbers and arithmetic #
Common Lisp supports various number types, including integers, floating-point numbers, ratios, and complex numbers. Arithmetic operations are straightforward:
- Addition:
(+ 1 2)
; Returns 3 - Subtraction:
(- 5 2)
; Returns 3 - Multiplication:
(* 4 6)
; Returns 24 - Division:
/ 10 3
; Returns 3.3333333 (floating point division) - Integer Division:
(floor 10 3)
; Returns 3 (integer part of the result) - Modulo:
(mod 10 3)
; Returns 1 (remainder)
Strings and characters #
Strings are sequences of characters. Characters are individual symbols.
- String Literals:
"Hello, world!"
- Character Literals:
#\A
(Note the#\
prefix) - String Concatenation:
(concatenate 'string "Hello" " " "world!")
; Returns “Hello world!” - String Length:
(length "Hello")
; Returns 5 - Accessing Characters:
(char "Hello" 0)
; Returns#\H
(index starts at 0)
Booleans and comparison #
Common Lisp uses T
(true) and NIL
(false) as boolean values. Comparison operators return T
or NIL
.
- Equality:
(= 1 1)
; ReturnsT
- Inequality:
(/= 1 2)
; ReturnsT
- Less Than:
(< 3 5)
; ReturnsT
- Greater Than:
(> 5 3)
; ReturnsT
- Less Than or Equal To:
(<= 3 3)
; ReturnsT
- Greater Than or Equal To:
(>= 5 3)
; ReturnsT
Lists: the fundamental data structure #
Lists are fundamental in Common Lisp. They are ordered sequences of elements, enclosed in parentheses.
- List Literals:
(1 2 3 4)
- Empty List:
NIL
(also representsfalse
) - Consing:
(cons 1 '(2 3))
; Returns(1 2 3)
(adds 1 to the beginning of the list) - Accessing Elements:
(car '(1 2 3))
; Returns1
(first element)(cdr '(1 2 3))
; Returns(2 3)
(rest of the list) - List Length:
(length '(1 2 3))
; Returns 3
Basic data type operations #
Common Lisp provides functions for various operations on data types:
- Type Checking:
(typep 1 'integer)
; ReturnsT
(checks if 1 is an integer) - Type Conversion:
(coerce 3.14 'integer)
; Returns3
(converts floating point to integer) - String to Number:
(parse-integer "123")
; Returns 123 - Number to String:
(format nil "~a" 123)
; Returns “123”
Functions and Control Flow #
Defining functions #
Functions in Common Lisp are defined using the defun
macro. The basic syntax is:
(defun function-name (parameter1 parameter2 ...)
"Optional documentation string"
body-expressions)
function-name
is a symbol that names the function. parameter1
, parameter2
, etc., are the function’s parameters. body-expressions
are the expressions that the function evaluates. The optional documentation string provides a description of the function.
Example:
(defun add (x y)
(+ x y))
This defines a function named add
that takes two parameters, x
and y
, and returns their sum.
Function arguments and parameters #
Parameters act as placeholders for the actual values (arguments) passed to the function when it’s called. Common Lisp supports various ways to handle arguments:
Required parameters: These parameters must be provided when calling the function. The
add
function above has two required parameters.Optional parameters: These parameters have default values if they are not provided during the function call. They are defined using
&optional
.
(defun greet (name &optional greeting)
(if greeting
(format t "~a, ~a!~%" greeting name)
(format t "Hello, ~a!~%" name)))
- Rest parameters: These parameters collect any remaining arguments into a list. They are defined using
&rest
.
(defun sum-all (&rest numbers)
(reduce #'+ numbers :initial-value 0))
Conditional statements (if, cond, when, unless) #
Common Lisp offers several ways to control the flow of execution based on conditions:
if
: The most basic conditional.
(if condition then-expression else-expression)
cond
: A more general conditional that allows for multiple conditions.
(cond
(condition1 expression1)
(condition2 expression2)
(t expression3)) ; t acts as an else clause
when
: Executes the expression only if the condition is true.
(when condition expression)
unless
: Executes the expression only if the condition is false.
(unless condition expression)
Loops (do, dotimes, dolist) #
Common Lisp provides several looping constructs:
do
: A general-purpose looping macro offering great flexibility. It allows you to specify initialization, test conditions, and step expressions.
(do ((i 0 (+ i 1))) ((> i 10) i)
(print i))
dotimes
: Iterates a specified number of times.
(dotimes (i 5)
(print i))
dolist
: Iterates over the elements of a list.
(dolist (x '(a b c))
(print x))
Function scope and closures #
Lexical Scoping: Common Lisp uses lexical scoping, meaning that the scope of a variable is determined by its position in the source code. A variable is accessible only within the function (or block) in which it’s defined.
Closures: A closure is a function that “remembers” its surrounding lexical environment even after it’s been created. This enables you to create functions that maintain state.
(let ((count 0))
(defun increment ()
(incf count)
count))
(print (increment)) ; Output: 1
(print (increment)) ; Output: 2
Here, increment
is a closure; it remembers the count
variable even after the let
block has finished executing.
Working with Lists #
List manipulation functions (car
, cdr
, cons
, append
, etc.)
#
Lists are fundamental in Common Lisp, and many built-in functions are designed to manipulate them efficiently. Here are some key functions:
car
: Returns the first element of a list.(car '(1 2 3))
returns1
.cdr
: Returns the rest of the list after removing the first element.(cdr '(1 2 3))
returns(2 3)
.cons
: Adds an element to the beginning of a list.(cons 0 '(1 2 3))
returns(0 1 2 3)
.append
: Concatenates multiple lists.(append '(1 2) '(3 4))
returns(1 2 3 4)
. Note thatappend
creates a new list; it doesn’t modify the original lists.reverse
: Reverses the order of elements in a list.(reverse '(1 2 3))
returns(3 2 1)
.length
: Returns the number of elements in a list.(length '(1 2 3))
returns3
.nth
: Returns the nth element of a list (0-indexed).(nth 1 '(1 2 3))
returns2
.member
: Checks if an element is present in a list.(member 2 '(1 2 3))
returns(2 3)
. ReturnsNIL
if the element is not found.
List comprehensions #
While Common Lisp doesn’t have list comprehensions in the same syntax as some other languages (like Python), you can achieve similar results using loop
:
(loop for x in '(1 2 3 4 5)
when (evenp x)
collect x) ; Returns (2 4)
This code iterates through the list (1 2 3 4 5)
, checks if each element is even (evenp
), and collects the even numbers into a new list.
Higher-order functions (map
, reduce
, filter
)
#
Higher-order functions take other functions as arguments. Common Lisp provides several useful ones:
map
: Applies a function to each element of a list.
(mapcar #'1+ '(1 2 3)) ; Returns (2 3 4) (#'1+ is a function that adds 1)
reduce
: Applies a binary function cumulatively to the elements of a list.
(reduce #'+ '(1 2 3)) ; Returns 6 (+ is the addition function)
remove-if
/remove-if-not
: Filters a list based on a predicate function.
(remove-if #'evenp '(1 2 3 4 5)) ; Returns (1 3 5)
Recursion in Lisp #
Recursion is a natural and elegant way to solve many problems in Lisp. Because lists are recursive data structures, recursive functions often map naturally to how lists are processed.
Example: A function to calculate the factorial of a number:
(defun factorial (n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
(print (factorial 5)) ; Returns 120
This function checks for the base case (n = 0
), and otherwise recursively calls itself with a smaller value of n
. Lisp’s memory management handles the recursive calls efficiently. However, very deep recursion might lead to stack overflow errors in some implementations, so it’s important to be mindful of potential recursion depth.
Macros and Metaprogramming #
Introduction to macros #
Macros are a powerful feature of Common Lisp that allow you to extend the language itself. Unlike functions, which operate on data, macros operate on code. They take code as input, transform it, and then return the transformed code which is then evaluated. This enables you to create new syntax, control code generation, and perform powerful metaprogramming tasks. Macros are defined using the defmacro
macro.
The key difference between a macro and a function is that a function operates on values, while a macro operates on code. A function evaluates its arguments before executing its body. A macro receives unevaluated code, transforms it, and then the transformed code is evaluated.
Simple macro examples #
Let’s create a simple macro that defines a function with a default value for an argument:
(defmacro defun-with-default (name params &body body)
`(defun ,name (,params &optional (arg2 10))
,@body))
(defun-with-default my-func (arg1)
(+ arg1 arg2))
(print (my-func 5)) ; Output: 15
(print (my-func 5 20)) ; Output: 25
This macro defun-with-default
takes a function name, parameters, and body as input and generates a defun
form with an optional parameter arg2
having a default value of 10.
Another example showing a simple macro for repetitive code:
(defmacro log-message (message)
`(format t "LOG: ~a~%" ,message))
(log-message "Starting process")
(log-message "Process complete")
This macro simplifies the logging process; instead of repeatedly writing (format t "LOG: ~a~%" ...)
you use the more readable (log-message "...")
.
Hygiene and macro expansion #
Hygiene: Hygiene is a crucial aspect of macro writing. It ensures that variables created within a macro don’t accidentally clash with variables in the surrounding code. Lisp macros are generally hygienic, meaning that variables created inside a macro are distinct from variables outside it.
Macro Expansion: The process of transforming a macro call into its expanded form is called macro expansion. You can see the expanded form using the macroexpand-1
function.
(defmacro my-macro (x)
`(+ ,x 1))
(macroexpand-1 '(my-macro 5)) ;Output: (+ 5 1)
This shows that (my-macro 5)
expands to (+ 5 1)
. Understanding macro expansion is essential for debugging macros and ensuring they behave as intended. Unhygienic macros can lead to unexpected behavior and bugs, so understanding and employing hygienic macro writing techniques is important for writing robust and maintainable code.
Object-Oriented Programming in Common Lisp #
Common Lisp’s object system, CLOS (Common Lisp Object System), is a powerful and flexible system that supports multiple inheritance and other advanced features. It’s different from class-based OOP in languages like Java or C++, but it offers similar capabilities and often more flexibility.
Classes and objects #
In CLOS, classes are defined using the defclass
macro. Objects are instances of classes, created using the make-instance
function.
(defclass dog ()
((name :initarg :name :accessor dog-name)
(breed :initarg :breed :accessor dog-breed)))
(defparameter my-dog (make-instance 'dog :name "Fido" :breed "Golden Retriever"))
(print (dog-name my-dog)) ; Output: Fido
(print (dog-breed my-dog)) ; Output: Golden Retriever
This defines a class dog
with slots (instance variables) name
and breed
. my-dog
is an instance of the dog
class, initialized with a name and breed. Accessor methods (dog-name
, dog-breed
) provide controlled access to the slots.
Methods and inheritance #
Methods are associated with classes and are called using generic functions (discussed below). Inheritance allows you to create new classes based on existing ones.
(defclass golden-retriever (dog)
((temperament :initform "Friendly" :accessor golden-retriever-temperament)))
(defmethod describe-dog ((dog dog))
(format t "Name: ~a, Breed: ~a~%" (dog-name dog) (dog-breed dog)))
(defmethod describe-dog ((golden golden-retriever))
(call-next-method)
(format t "Temperament: ~a~%" (golden-retriever-temperament golden)))
(defparameter my-golden (make-instance 'golden-retriever :name "Buddy"))
(describe-dog my-dog) ;Output: Name: Fido, Breed: Golden Retriever
(describe-dog my-golden) ;Output: Name: Buddy, Breed: , Temperament: Friendly
golden-retriever
inherits from dog
. describe-dog
is a generic function with methods specialized for dog
and golden-retriever
. The call-next-method
in describe-dog
for golden-retriever
calls the method defined for the parent class dog
.
Generic functions #
Generic functions are central to CLOS. They are functions that can dispatch to different methods based on the classes of their arguments. This is known as multiple dispatch.
The example above shows a generic function describe-dog
. The system determines which method to call based on the class of the dog
argument. This allows for flexible and extensible behavior. You define generic functions using defgeneric
and methods using defmethod
. If no method matches the argument types, CLOS will signal an error. You can create methods with increasing specificity (e.g., handling a subclass before the parent class). This is the power of multiple dispatch, allowing for fine-grained control over object behavior based on their type.
Advanced Topics #
Packages and namespaces #
Common Lisp uses packages to manage namespaces. A package is a collection of symbols, preventing naming conflicts between different parts of your code or libraries. The in-package
function switches to a specific package. export
makes symbols visible to other packages, and import
brings symbols from other packages into the current one. Packages are crucial for organizing large projects and avoiding symbol clashes when using external libraries. Understanding how packages work is essential for managing dependencies and creating modular code.
For example:
(in-package :cl-user) ; Switch to the user package
(defpackage :my-utils
(:use :cl)
(:export :my-function))
(in-package :my-utils)
(defun my-function (x)
(+ x 10))
(in-package :cl-user)
(my-utils:my-function 5) ; Accessing the function from another package
This demonstrates creating and using a package my-utils
which exports a function my-function
.
Exception handling #
Common Lisp provides robust exception handling mechanisms using handler-case
, ignore-errors
, and error
.
handler-case
allows you to specify handlers for specific conditions:
(handler-case
(progn
(/ 10 0) ;Will cause a division by zero error
(print "This won't be printed"))
(division-by-zero (condition)
(format t "Division by zero error: ~a~%" condition))
(error (condition)
(format t "Generic error: ~a~%" condition)))
ignore-errors
suppresses error messages:
(ignore-errors (/ 10 0)) ; No error message is shown
error
signals an error condition.
Input/output operations #
Common Lisp provides functions for various I/O operations. print
and format
are used for output to the standard output stream. read
reads input from the standard input stream. More advanced I/O operations require using streams, which are objects representing sources or destinations of data (files, network connections, etc.). The functions open
, close
, read-line
, read-char
, etc., are commonly used for file I/O.
Working with files #
To work with files, you typically use open
to create a stream, then perform I/O operations using functions like read-line
or write-line
, and finally close
the stream when you’re done.
(with-open-file (stream "my-file.txt" :direction :output :if-exists :supersede)
(format stream "Hello, world!~%"))
This creates a file named “my-file.txt” and writes “Hello, world!” to it. Error handling (using handler-case
) is recommended for robust file operations.
Common Lisp libraries #
Numerous libraries extend Common Lisp’s functionality. These libraries cover various domains such as:
- Web frameworks: (e.g., Hunchentoot, Caveman)
- Database interaction: (e.g., PostgreSQL, MySQL connectors)
- GUI programming: (e.g., Clack, McCLIM)
- Numerical computation: (e.g., CL-PPCRE)
- Artificial intelligence: (various libraries for machine learning, etc.)
Using these libraries requires understanding how to load and use external code, often through package management systems or by directly loading .fas
or .asd
files. Consulting the documentation for each library is crucial.
Building a Simple Project #
This section guides you through building a small Common Lisp application, illustrating project setup, coding, testing, and debugging.
Project setup and organization #
A good project structure is vital, even for small projects. A common approach is to have separate files for different parts of your application. For a simple project, a single file might suffice, but for larger projects, organizing your code into modules and using a package system is highly recommended. Consider using a build system (e.g., ASDF) to manage dependencies and compilation.
For a very basic example, let’s assume all code resides within a single file named my-app.lisp
.
A small example application #
Let’s build a simple program that calculates the factorial of a number.
(defun factorial (n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
(defun main ()
(format t "Enter a non-negative integer: ")
(let ((n (read)))
(if (>= n 0)
(format t "Factorial of ~a is ~a~%" n (factorial n))
(format t "Please enter a non-negative integer.~%"))))
(main)
This code defines a factorial
function and a main
function that prompts the user for input, calculates the factorial, and prints the result. Error handling (checking for negative input) is included. To run this, save it as my-app.lisp
and load it into your Lisp environment using (load "my-app.lisp")
.
Testing and debugging #
Testing is crucial for reliable software. For small projects, simple tests within the REPL are sufficient. For larger projects, consider using a testing framework like FiveAM.
Debugging: The REPL itself is a powerful debugging tool. You can step through your code using the debugger (by placing breakpoints or letting exceptions trigger the debugger), inspect variables, and evaluate expressions interactively. Common Lisp implementations usually provide debugging tools accessible through the REPL or integrated into your IDE.
Example of simple testing in the REPL:
- Load your code:
(load "my-app.lisp")
- Test the factorial function:
(factorial 5)
(Should return 120) - Test with edge cases:
(factorial 0)
(Should return 1),(factorial -1)
(Should signal an error, or handle it gracefully in your code). - Test the
main
function by running it.
For more advanced debugging:
- Use the
trace
macro to print function entry and exit points along with argument and return values. - Use the debugger’s stepping capabilities to follow the execution flow.
- Use the
inspect
function to examine the state of objects.
Remember that good code style (clear variable names, meaningful comments, modular design) significantly aids in testing and debugging.
Further Learning Resources #
This section points you to resources for continued learning and engagement with the Common Lisp community.
Books and online tutorials #
Several excellent books and online tutorials cater to various skill levels:
Practical Common Lisp by Peter Seibel: A widely recommended book for beginners, providing a gentle introduction to the language and its features.
Land of Lisp by Conrad Barski: A more playful and engaging introduction, using game development as a vehicle for learning.
ANSI Common Lisp by Paul Graham: A classic text, though it might be more challenging for absolute beginners.
On Lisp by Paul Graham: Focuses on macros and metaprogramming, ideal once you have a solid grasp of the basics.
Let Over Lambda by Doug Hoyte: Explores functional programming paradigms within Common Lisp.
Numerous online tutorials are available:
Common Lisp the Language (CLtL2): While a comprehensive reference, it might be dense for beginners. It’s more suitable as a reference guide after learning the basics. The online version is readily searchable.
Various YouTube channels and blog posts: Search for “Common Lisp tutorial” or “Common Lisp introduction” on YouTube and various programming blogs. Many excellent resources are available, covering various aspects of the language and its libraries.
Exercism.io: This platform offers coding exercises and feedback from the community, helping to solidify your understanding of Common Lisp concepts.
The choice of resource depends on your learning style and preferences. Beginners might find Seibel’s book or Barski’s book a more approachable starting point.
Common Lisp communities and forums #
Engaging with the Common Lisp community is an invaluable part of the learning process. Here are some key places to connect:
comp.lang.lisp (Usenet): A long-standing newsgroup where you can find discussions, ask questions, and share your knowledge.
Common Lisp subreddit (r/lisp): A vibrant online community on Reddit for discussing Common Lisp, asking questions, and sharing resources.
The Common Lisp subreddit (r/commonlisp): Another Reddit community dedicated to Common Lisp.
lisp-list (mailing list): A long-running mailing list for discussions related to Common Lisp.
Slack communities: There are several Common Lisp Slack communities where you can interact with other developers and get help with your projects. You may find links to these communities on the Common Lisp subreddit.
These communities offer support, guidance, and opportunities for collaboration. Don’t hesitate to ask questions; the community is generally welcoming and helpful to newcomers.