Getting started
Introduction
Common Lisp is a multi-paradigm, dynamically-typed programming language with powerful macro system and interactive development workflow via SBCL (Steel Bank Common Lisp) and SLIME (Superior Lisp Interaction Mode for Emacs).
Installation
# macOS (Homebrew)
brew install sbcl
# Ubuntu/Debian
apt-get install sbcl
# Arch Linux
pacman -S sbcl
# Quicklisp (package manager)
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp
SLIME Setup
;; ~/.emacs or ~/.emacs.d/init.el
(setq inferior-lisp-program "sbcl")
(load (expand-file-name "~/quicklisp/slime-helper.el"))
(add-to-list 'load-path "~/quicklisp/dists/quicklisp/software/slime-v2.28/")
(require 'slime-autoloads)
(slime-setup '(slime-fancy slime-company))
# Start SLIME in Emacs
M-x slime
Hello World
;; Print to stdout
(print "Hello, World!")
;; Format output
(format t "Hello, ~a!~%" "World")
;; Define function
(defun greet (name)
(format t "Hello, ~a!~%" name))
(greet "Lisp")
SLIME Keybindings
Essential Evaluation
| Keybinding | Description |
|---|---|
C-x C-e |
Eval expression before point |
C-M-x |
Eval top-level form (defun/defvar) |
C-c C-k |
Compile and load current file |
C-c C-c |
Compile top-level form |
C-c C-l |
Load file |
C-c C-r |
Eval region |
Navigation & Inspection
| Keybinding | Description |
|---|---|
M-. |
Jump to definition |
M-, |
Pop back from definition |
C-c C-d d |
Describe symbol |
C-c C-d f |
Describe function |
C-c C-d h |
Lookup in HyperSpec |
C-c C-d a |
Apropos (search symbols) |
REPL Interaction
| Keybinding | Description |
|---|---|
C-c C-z |
Switch to REPL |
C-c M-o |
Clear REPL buffer |
M-p / M-n |
Previous/next input history |
C-c C-o |
Clear last REPL output |
, |
SLIME shortcut (in REPL) |
Debugging
| Keybinding | Description |
|---|---|
v |
View source (in debugger) |
e |
Eval in frame (in debugger) |
q |
Quit debugger |
a |
Abort to top level |
c |
Continue (invoke restart) |
0-9 |
Invoke restart by number |
Compilation
| Keybinding | Description |
|---|---|
C-c C-k |
Compile file |
C-c C-c |
Compile defun |
C-c M-k |
Compile and load file |
M-n / M-p |
Next/previous note (warnings) |
Macroexpansion
| Keybinding | Description |
|---|---|
C-c M-m |
Macroexpand-1 |
C-c M-M |
Macroexpand-all |
C-c C-t |
Trace function |
C-c C-u |
Untrace function |
S-expressions
Basic Syntax
;; Atoms
42 ; integer
3.14 ; float
"string" ; string
'symbol ; symbol
:keyword ; keyword
t ; true
nil ; false/empty list
;; Lists (code and data)
'(1 2 3) ; list literal
(list 1 2 3) ; construct list
(+ 1 2) ; function call
Quoting
;; Quote (prevent evaluation)
'(+ 1 2) ; => (+ 1 2)
(quote (+ 1 2)) ; same as above
;; Quasiquote (template)
`(1 2 ,(+ 1 2)) ; => (1 2 3)
`(1 ,@'(2 3)) ; => (1 2 3)
;; Unquote
(let ((x 10))
`(value is ,x)) ; => (VALUE IS 10)
List Operations
;; Constructors
(cons 1 '(2 3)) ; => (1 2 3)
(list 1 2 3) ; => (1 2 3)
(append '(1 2) '(3 4)) ; => (1 2 3 4)
;; Accessors
(car '(1 2 3)) ; => 1
(cdr '(1 2 3)) ; => (2 3)
(first '(1 2 3)) ; => 1
(rest '(1 2 3)) ; => (2 3)
(nth 1 '(a b c)) ; => B
;; Predicates
(null nil) ; => T
(listp '(1 2)) ; => T
(consp '(1 2)) ; => T
Functions
Defining Functions
;; Basic function
(defun add (a b)
(+ a b))
;; Optional parameters
(defun greet (&optional (name "World"))
(format t "Hello, ~a!~%" name))
;; Keyword parameters
(defun make-user (&key name age)
(list :name name :age age))
(make-user :name "Alice" :age 30)
;; Rest parameters
(defun sum (&rest numbers)
(reduce #'+ numbers))
(sum 1 2 3 4) ; => 10
Lambda & Closures
;; Anonymous function
(lambda (x) (* x x))
;; Using lambda
(funcall (lambda (x) (* x x)) 5) ; => 25
(mapcar (lambda (x) (* x x)) '(1 2 3)) ; => (1 4 9)
;; Closure
(defun make-adder (n)
(lambda (x) (+ x n)))
(let ((add5 (make-adder 5)))
(funcall add5 10)) ; => 15
Higher-Order Functions
;; Map
(mapcar #'1+ '(1 2 3)) ; => (2 3 4)
(mapcar #'sqrt '(1 4 9)) ; => (1.0 2.0 3.0)
;; Filter
(remove-if-not #'evenp '(1 2 3 4)) ; => (2 4)
(remove-if #'oddp '(1 2 3 4)) ; => (2 4)
;; Reduce
(reduce #'+ '(1 2 3 4)) ; => 10
(reduce #'max '(3 1 4 1 5)) ; => 5
;; Function composition
(funcall (compose #'sqrt #'abs) -16) ; => 4.0
Variables & Scope
Global Variables
;; Special (dynamic) variable
(defvar *database* nil
"Global database connection.")
(defparameter *debug* t
"Debug mode (always set).")
;; Constant
(defconstant +pi+ 3.14159
"Value of pi.")
Local Binding
;; Lexical binding
(let ((x 10)
(y 20))
(+ x y)) ; => 30
;; Sequential binding
(let* ((x 10)
(y (* x 2)))
y) ; => 20
;; Dynamic binding
(defvar *x* 1)
(let ((*x* 2))
(print *x*)) ; => 2
Assignment
;; Set variable
(setq x 10)
(setf x 20)
;; Set place
(setf (car lst) 'new-value)
(setf (gethash 'key table) 'value)
;; Multiple assignment
(setf x 1 y 2 z 3)
;; Increment
(incf x) ; x = x + 1
(decf y 5) ; y = y - 5
Control Flow
Conditionals
;; If
(if (> x 0)
'positive
'negative)
;; When (no else)
(when (> x 0)
(print "positive")
x)
;; Unless
(unless (zerop x)
(/ 1 x))
;; Cond
(cond
((< x 0) 'negative)
((= x 0) 'zero)
((> x 0) 'positive))
;; Case
(case x
(1 'one)
(2 'two)
(t 'other))
Loops
;; Loop macro (powerful iteration)
(loop for i from 1 to 10
collect (* i i))
(loop for x in '(1 2 3)
sum x)
;; Dotimes
(dotimes (i 5)
(print i))
;; Dolist
(dolist (item '(a b c))
(print item))
;; Do (general iteration)
(do ((i 0 (1+ i)))
((>= i 5) 'done)
(print i))
Loop Examples
;; Collect
(loop for i from 1 to 5
collect (* i i))
;; => (1 4 9 16 25)
;; Sum/Count
(loop for x in '(1 2 3 4)
sum x) ; => 10
;; Conditional
(loop for x in '(1 2 3 4)
when (evenp x)
collect x) ; => (2 4)
;; Multiple clauses
(loop for x from 1 to 10
if (evenp x)
collect x into evens
else
collect x into odds
finally (return (values evens odds)))
Macros
Defining Macros
;; Simple macro
(defmacro when-positive (test &body body)
`(when (> ,test 0)
,@body))
;; Usage
(when-positive x
(print "positive")
x)
;; With-style macro
(defmacro with-gensyms (syms &body body)
`(let ,(mapcar (lambda (s)
`(,s (gensym)))
syms)
,@body))
Macroexpansion
;; Expand once
(macroexpand-1 '(when-positive x (print x)))
;; Full expansion
(macroexpand '(when-positive x (print x)))
;; In SLIME
C-c M-m ; macroexpand-1
C-c M-M ; macroexpand-all
Gensyms (Hygiene)
;; Problem: variable capture
(defmacro bad-swap (a b)
`(let ((temp ,a))
(setf ,a ,b)
(setf ,b temp)))
;; Solution: gensym
(defmacro swap (a b)
(let ((temp (gensym)))
`(let ((,temp ,a))
(setf ,a ,b)
(setf ,b ,temp))))
CLOS (Object System)
Classes
;; Define class
(defclass person ()
((name
:initarg :name
:accessor person-name)
(age
:initarg :age
:accessor person-age
:initform 0)))
;; Create instance
(make-instance 'person
:name "Alice"
:age 30)
;; Inheritance
(defclass employee (person)
((company
:initarg :company
:accessor employee-company)))
Methods
;; Generic function + method
(defgeneric greet (person))
(defmethod greet ((p person))
(format t "Hello, ~a!~%" (person-name p)))
;; Specialized method
(defmethod greet ((e employee))
(format t "Hello, ~a from ~a!~%"
(person-name e)
(employee-company e)))
;; Multiple dispatch
(defmethod combine ((a number) (b number))
(+ a b))
(defmethod combine ((a string) (b string))
(concatenate 'string a b))
Slot Access
;; Accessor
(person-name p)
(setf (person-name p) "Bob")
;; Slot-value
(slot-value p 'name)
(setf (slot-value p 'name) "Charlie")
;; With-slots
(with-slots (name age) person
(format t "~a is ~a years old" name age))
Conditions & Restarts
Signaling Conditions
;; Define condition
(define-condition invalid-input (error)
((value :initarg :value :reader invalid-input-value))
(:report (lambda (condition stream)
(format stream "Invalid input: ~a"
(invalid-input-value condition)))))
;; Signal error
(error 'invalid-input :value x)
;; Signal warning
(warn "This is a warning")
;; Signal condition (non-error)
(signal 'my-condition)
Handling Conditions
;; Handler-case (like try/catch)
(handler-case
(/ 1 0)
(division-by-zero ()
(print "Cannot divide by zero")
nil))
;; Handler-bind (lower-level)
(handler-bind
((error (lambda (c)
(print c)
(invoke-restart 'use-default))))
(do-something))
;; Ignore errors
(ignore-errors
(/ 1 0))
Restarts
;; Establish restart
(restart-case
(error 'invalid-input :value x)
(use-value (value)
:report "Use a different value"
:interactive (lambda () (list (read)))
value)
(use-default ()
:report "Use default value"
0))
;; Invoke restart
(invoke-restart 'use-value 42)
;; In SLIME debugger:
;; - Type restart number (0-9)
;; - Or: (invoke-restart 'restart-name)
Packages & Systems
Package Definition
;; defpackage
(defpackage :my-app
(:use :cl)
(:export :main
:run
:*config*))
(in-package :my-app)
;; Use package
(use-package :alexandria)
;; Import specific symbols
(import '(alexandria:hash-table-keys
alexandria:hash-table-values))
ASDF System
;; my-app.asd
(defsystem "my-app"
:description "My application"
:version "0.1.0"
:author "Your Name"
:license "MIT"
:depends-on (:alexandria
:cl-ppcre
:hunchentoot)
:components ((:module "src"
:components
((:file "package")
(:file "utils" :depends-on ("package"))
(:file "main" :depends-on ("utils"))))))
;; Load system
(asdf:load-system :my-app)
;; In SLIME
,load-system my-app
Quicklisp
;; Load library
(ql:quickload :dexador)
(ql:quickload '(:hunchentoot :cl-json))
;; Search for library
(ql:system-apropos "http")
;; Update
(ql:update-all-dists)
;; List installed
(ql:list-installed)
SBCL-Specific Features
SB-EXT (Extensions)
;; Exit SBCL
(sb-ext:exit)
(sb-ext:quit)
;; Save image
(sb-ext:save-lisp-and-die "my-app"
:executable t
:toplevel #'my-app:main)
;; Run external program
(sb-ext:run-program "/bin/ls" '("-la")
:output *standard-output*)
;; Get environment variable
(sb-ext:posix-getenv "HOME")
;; Weak pointers
(sb-ext:make-weak-pointer obj)
(sb-ext:weak-pointer-value wp)
SB-THREAD (Threading)
;; Create thread
(sb-thread:make-thread
(lambda () (print "Hello from thread")))
;; Join thread
(let ((thread (sb-thread:make-thread #'worker)))
(sb-thread:join-thread thread))
;; Mutex
(defvar *lock* (sb-thread:make-mutex))
(sb-thread:with-mutex (*lock*)
(critical-section))
;; Semaphore
(defvar *sem* (sb-thread:make-semaphore))
(sb-thread:signal-semaphore *sem*)
(sb-thread:wait-on-semaphore *sem*)
SB-ALIEN (FFI)
;; Define C function
(sb-alien:define-alien-routine getpid
sb-alien:int)
(getpid) ; => process ID
;; Load shared library
(sb-alien:load-shared-object "libm.so")
;; Define external function
(sb-alien:define-alien-routine sqrt
sb-alien:double
(x sb-alien:double))
Compiler Options
;; Optimize for speed
(declaim (optimize (speed 3) (safety 0)))
;; Optimize for debugging
(declaim (optimize (speed 0) (safety 3) (debug 3)))
;; Inline function
(declaim (inline my-fast-function))
;; Type declarations
(declaim (ftype (function (fixnum fixnum) fixnum) add))
(defun add (a b)
(declare (type fixnum a b))
(+ a b))
REPL Workflow
SLIME REPL Commands
;; Shortcuts (type , in REPL)
,help ; Show help
,cd <dir> ; Change directory
,pwd ; Print working directory
,restart-inferior-lisp ; Restart Lisp
;; Package management
,in-package :pkg ; Change package
,load-system sys ; Load ASDF system
;; Debugging
,trace fn ; Trace function
,untrace fn ; Untrace function
Interactive Development
;; 1. Write function in .lisp file
(defun factorial (n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
;; 2. Eval with C-M-x (cursor on defun)
;; 3. Test in REPL
CL-USER> (factorial 5)
120
;; 4. Modify and re-eval instantly
(defun factorial (n)
(if (<= n 1)
1
(* n (factorial (1- n))))) ; Use 1- instead
;; C-M-x again - no restart needed!
Debugging Workflow
;; 1. Set breakpoint
(defun buggy-fn (x)
(break) ; Breakpoint
(/ 100 x))
;; 2. Call function
(buggy-fn 0)
;; 3. In debugger (SLDB buffer):
;; - Press 'v' to view source
;; - Press 'e' to eval in frame
;; - Press 'c' to continue
;; - Press number to invoke restart
;; - Press 'a' to abort to top level
;; 4. Invoke restart interactively
;; - Select "RETURN-VALUE" restart
;; - Enter: 0
Practical Examples
Web Server (Hunchentoot)
(ql:quickload :hunchentoot)
(defpackage :my-web-app
(:use :cl :hunchentoot))
(in-package :my-web-app)
;; Define handler
(define-easy-handler (hello :uri "/hello") (name)
(setf (content-type*) "text/html")
(format nil "<html><body>Hello, ~a!</body></html>"
(or name "World")))
;; Start server
(defvar *server* (start (make-instance 'easy-acceptor :port 8080)))
;; Stop server
(stop *server*)
JSON Processing
(ql:quickload :jonathan)
;; Parse JSON
(jonathan:parse "{\"name\":\"Alice\",\"age\":30}")
;; => #<HASH-TABLE :TEST EQUAL :COUNT 2 {10034F6BB3}>
;; Generate JSON
(jonathan:to-json '(:name "Alice" :age 30))
;; => "{\"name\":\"Alice\",\"age\":30}"
;; With CL-JSON
(ql:quickload :cl-json)
(json:decode-json-from-string "{\"name\":\"Alice\"}")
(json:encode-json-to-string '((:name . "Alice") (:age . 30)))
File I/O
;; Read file
(with-open-file (in "input.txt" :direction :input)
(loop for line = (read-line in nil)
while line
collect line))
;; Write file
(with-open-file (out "output.txt"
:direction :output
:if-exists :supersede)
(format out "Hello, file!~%"))
;; Read all
(uiop:read-file-string "file.txt")
;; Write all
(uiop:dump-string "content" "file.txt")
Testing (FiveAM)
(ql:quickload :fiveam)
(defpackage :my-app-tests
(:use :cl :fiveam))
(in-package :my-app-tests)
(def-suite my-app-suite
:description "Test suite for my-app")
(in-suite my-app-suite)
(test addition
"Test addition function"
(is (= 4 (+ 2 2)))
(is (= 0 (+ -1 1))))
;; Run tests
(run! 'my-app-suite)
Common Gotchas
Case Sensitivity
;; Symbols are case-insensitive (upcased by reader)
'foo ; => FOO
'Foo ; => FOO
'FOO ; => FOO
;; Escape for case-sensitive
'|Foo| ; => |Foo| (preserves case)
;; Strings are case-sensitive
"foo" "Foo" ; Different strings
Equality Predicates
;; EQ - same object (identity)
(eq 'a 'a) ; => T
(eq 3 3) ; => T (usually, but not guaranteed for numbers)
;; EQL - same object or same number/char
(eql 3 3) ; => T (guaranteed)
(eql 3.0 3.0) ; => T
;; EQUAL - structural equality
(equal '(1 2) '(1 2)) ; => T
(equal "abc" "abc") ; => T
;; EQUALP - case-insensitive strings, type-insensitive numbers
(equalp "ABC" "abc") ; => T
(equalp 3 3.0) ; => T
Namespace Separation
;; Separate namespaces for functions and variables
(defun list (x) (print x)) ; Shadows CL:LIST!
(list 1 2 3) ; Error: 1 is not a function
;; Fix: use different name or FUNCALL
(funcall #'list 1 2 3) ; Calls CL:LIST
Macroexpansion Time
;; Macros expand at compile time
(defvar *x* 1)
(defmacro use-x ()
`(print ,*x*)) ; Captures *x* at macro definition time!
(use-x) ; Prints 1
(setq *x* 2)
(use-x) ; Still prints 1!
;; Fix: don't splice compile-time values
(defmacro use-x ()
'(print *x*)) ; Expands to (print *x*), evaluated at runtime
Also see
- Common Lisp HyperSpec - Official language reference
- SBCL Manual - SBCL-specific features and extensions
- SLIME User Manual - Complete SLIME documentation
- Practical Common Lisp - Free online book by Peter Seibel
- Quicklisp - Library manager for Common Lisp
- Awesome Common Lisp - Curated list of libraries
- Common Lisp Cookbook - Practical recipes and patterns