NexusCS

Common Lisp

Lisp
Quick reference for Common Lisp development with SBCL and SLIME integration.
sbcl
slime
lisp
functional

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