Common Lisp provides two ways to create global variables:
DEFVAR and DEFPARAMETER.
Both forms take a variable name, an initial value, and an optional documentation string.
Global variables are conventionally named with names that start and end with * (e.g., *db*).
(defvar *count* 0
"Count of widgets made so far.")
(defparameter *gap-tolerance* 0.001
"Tolerance to be allowed in widget gaps.")
DEFCONSTANT defines a named global constant.
(sbcl) (defconstant +pi+ 3.1415926 "Approximation of PI")
(sbcl) +pi+
3.1415926
SETF is the general-purpose assignment operator in Common Lisp.
It is used to set or update the value of variables, places (like array elements, object fields, etc.), and more.
(setf place value)
SETF can also assign to multiple places in sequence.
(setf x 1)
(setf y 1)
(setf x 1 y 2)
SETF returns the newly assigned value,
so you can also nest calls to SETF as in the following expression, which assigns both x and y the same random value: (setf x (setf y (random 10)))
You could increment / decrement a number with SETF:
(setf x (+ x 1)); (incf x)
(setf x (+ x 10)); (incf x 10)
(setf x (- x 1)); (decf x)
ROTATEF: swaps values in-place by rotating them left-wise, returns NIL.
NOTE: All places (variables, array elements, etc.) must be writable with SETF.
(rotatef <place1> <place2> ... <placeN>)
place1 <- place2
place2 <- place3
...
placeN <- original place1
(let ((a 1) (b 2) (c 3))
(rotatef a b c)
(list a b c))
(2 3 1)
SHIFTF shifts values left-wise and returns the original first value.
(shiftf <place1> <place2> ... <placeN> <new-value>)
place1 <- place2
place2 <- place3
...
placeN <- new-value
return <- original place1
(let ((a 1) (b 2) (c 3))
(shiftf a b c 100)) ;; returns 1
Difference between LET and LET*
LET: All variables are initialized in parallel.
LET*:
Variables are initialized one by one in sequence,
left to right, and each one can use the previous ones.
(let* ((a 2)
(b (+ a 3)) ; b = 2 + 3 = 5
(c (* b 4))) ; c = 5 * 4 = 20
(list a b c)) ; (2 5 20)
Difference between DEFVAR and DEFPARAMETER
DEFVAR: re-decl does not overwrite an existing value (can be later changed with assignment).
A backslash ( \ ) escapes the next character, causing it to be included in the string regardless of what it is.
"foo" ; foo
"f\oo" ; foo, because `\o` escapes `o`, treating it iterally like the character `o`.
"fo\\o" ; fo\o
"fo\'o" ; fo'o
"fo\"o" ; fo"o
Basics about Symbol
There are 10 characters that serve other syntactic purposes can NOT appear in names:
Open Parentheses ( ( )
Close Parentheses ( ) )
Double Quote ( " )
Single Quote ( ' )
Backtick ( ` )
Comma ( , )
Colon ( : )
Semicolon ( ; )
Backslash ( \ )
Vertical Bar ( | )
If you really want to use those character,
try to escape them with a backslash ( \ )
or surround the part of the name containing characters that need escaping
with vertical bars ( | ).
NOTE:
The symbol names are case-insensitive,
it converts all unescaped characters to upper case,
like foo, Foo, and FOO are the same.
However, the f\o\o and |foo| will preserve the case, they are read as foo.
Keyword Symbols
Keyword symbols are prefixed with a colon (e.g., :name, :id).
This prefix automatically makes them part of the KEYWORD package.
Unlike regular symbols, keyword symbols evaluate to themselves.
Keyword symbols are often used as keys in property list.
> (setq person '(:name "Alice" :age 20))
> (getf person :name)
"Alice"
; Btw, (setq person (:name "Alice" :age 20)) is invalid, because Lisp will tru to eval as if :name were a function.
; You can also use (setq person (list :name "Alice" :age 20)) instead.
Many libraries use keyword symbols for configuration settings.
(open "settings.conf", :direction :output)
if Statement
Syntax: (if <cond> <then> [<else>])
cond is a condition that eval to either NIL (false) or non-NIL (true).
then is the expression to exec if cond is true.
else is the optional expression to exec if cond is false.
The if expression returns the result of the evaluated then or else form.
(let ((x 10))
(if (> x 5) (+ x 10) (- x 10)))
Basics about quote
It is used to prevent an expression from being evaluated.
Syntax:
(quote <expr>) or shorthandly '<expr>
Example Usage:
> (quote (+ 1 2))
(+ 1 2)
> '(+ 1 2)
(+ 1 2)
> (+ 1 2)
3
> 'a
A
> '\a
|a|
> (setq a 100)
100
> a
100
> 'a
A
> (symbol-value 'a)
100
> (setq a 'b) ; a holds the symbol B
B
> (set a 100) ; same as (set 'b 100)
100
> b
100
(setq a 100) and (setq 'a 100) are NOT the same
(SETQ expects an unquoted symbol in the variable position).
If you want to assign to a variable by a symbol at runtime, use the symbol's value cell:
(setf (symbol-value 'a) 100) or (set 'a 100)
'a is a symbol ONLY when evaluated, normally it is treated as an quoted expression (quote A).
Quick contrasts:
(setq a 100): special operator; does NOT evaluate the variable name; sets the variable A.
(setf a 100): for simple variables same as SETQ.
(set 'a 100): function; evaluates 'a to A; sets A's global/special value cell.
(set a 100): function; evaluates a (must be a symbol); sets that symbol's global/special value.
True, False and Equality
The symbol t or T is conventionally treated as true in Common Lisp.
(if t "True" "False") ; returns "True"
The expressions nil, 'nil, () and '() are considered false in Common Lisp.
You can define keyword aliases to match external names to internal variable names:
(defun fruit (&key ((:apple a)) ((:banana b) 0) ((:cherry c) 0 c-supplied-p))
(list a b c c-supplied-p))
(fruit :apple 10 :banana 20 :cherry 30)
;; => (10 20 30 T)
Combining &rest and &key
You can use both together if you want the raw list of keywords as well.
(defun foo (&rest args &key a b c)
(list args a b c))
(foo :a 1 :b 2 :c 3)
;; => ((:A 1 :B 2 :C 3) 1 2 3)
Returning values
A function returns the value of its last evaluated form.
You can use RETURN-FROM to exit early with a specific value.
FUNCALL → use when you know the arguments at write time.
APPLY → use when arguments are already in a list.
Example: ASCII plotter using FUNCALL
This function takes another function as an argument and prints an ASCII bar chart of its values.
(defun plot (fn min max step)
(loop for i from min to max by step do
(loop repeat (funcall fn i) do (format t "*"))
(format t "~%")))
(plot #'exp 0 4 0.5)
*
*
**
****
*******
************
********************
*********************************
******************************************************
Here, fn can be any function: built-in, user-defined, or anonymous.
Combining lists with REDUCE
REDUCE applies a function cumulatively to elements of a sequence to "fold" it into one result.
mapcar, mapcan, and map are higher-order functions: they use other functions to perform their operations.
Summary
Functions in Lisp are objects: you can store, pass, and return them.
#'name retrieves a function object; 'name refers to a symbol.
FUNCALL → call a function with known arguments.
APPLY → call a function with a list of arguments.
REDUCE → combine list elements using a function.
LAMBDA → create anonymous functions; closures can capture variables.
MAPCAR and friends → apply functions elementwise to collections.
Treating functions as data enables higher-order programming: passing logic, not just numbers.
The difference between (3 4 5) and '(3 4 5)
(3 4 5) is a function call.
In Lisp, anything inside parentheses is treated as a function call unless you explicitly prevent evaluation.
'(3 4 5) is a quoted list: a literal value.
The single quote ( ' ) is shorthand for (quote ...).
Macro
What is a macro?
In Common Lisp, a macro is code that writes other code.
Unlike a normal function (which runs at runtime), a macro runs at compile-time / macro-expansion-time.
It receives its arguments as unevaluated Lisp forms (s-expressions) and returns a new Lisp form, which is then compiled / evaluated in place of the original macro call.
So the lifecycle looks like this:
You write: (my-macro arg1 arg2 ...)
Lisp calls the macro as a transformer and gets back some new code: e.g. (if ... (progn ...))
That resulting code is what actually gets run at runtime.
macro-name: symbol naming the macro (how you'll call it).
lambda-list: parameter list, but it receives unevaluated forms.
It supports almost everything defun does: &optional, &rest, &key, and also &body.
doc-string: optional documentation string, visible via (documentation 'macro-name 'function).
body-forms: these run at macro-expansion-time and must return code (a Lisp form), not the final result of the computation.
Macro parameters and &body
In macros, you often want to capture "the rest of the forms" as a list, similar to &rest.
&body is like &rest but is used by convention for "body-forms" and some tools / editors treat it specially.
(defmacro my-when (test &body body)
"If TEST is true, execute BODY like (progn ...)."
...)
Here, body is a list of the forms the user wrote inside the macro call.
Backquote, comma, and comma-at
When writing macros, you often need to build lists that represent code.
The backquote ( ` ) notation is a handy way to write "templates" for lists with parts that are computed.
Inside a backquoted form:
,expr: "unquote": evaluate expr and insert its value.
,@expr: "unquote-splicing": evaluate expr and splice its elements into the surrounding list.
Backquote Syntax
Equivalent List-Building Code
Result
`(a (+ 1 2) c)
(list 'a '(+ 1 2) 'c)
(a (+ 1 2) c)
`(a ,(+ 1 2) c)
(list 'a (+ 1 2) 'c)
(a 3 c)
`(a (list 1 2) c)
(list 'a '(list 1 2) 'c)
(a (list 1 2) c)
`(a ,(list 1 2) c)
(list 'a (list 1 2) 'c)
(a (1 2) c)
`(a ,@(list 1 2) c)
(append (list 'a) (list 1 2) (list 'c))
(a 1 2 c)
Full DEFMACRO form
General syntax (simplified):
(defmacro macro-name (lambda-list
&optional doc-string
&rest body)
"doc-string"
;; declarations (optional)
;; body must compute and return code
...)
lambda-list: like in defun: supports &optional, &key, &rest, &body, and even destructuring (covered elsewhere in your notes).
doc-string: optional, shown by DOCUMENTATION.
declarations: compiler hints (e.g. (declare (special x))); you can add them between the doc-string and the body.
body: produces the expansion form. It does not directly perform the user's work; it returns code that will be run later.
A simple macro: MY-WHEN
(defmacro my-when (test &body body)
"If TEST is true, execute BODY like (progn ...)."
`(if ,test
(progn ,@body)))
(my-when (> x 10)
(print "positive")
(incf x))
What does Lisp actually see after macro-expansion?
(if (> x 10)
(progn
(print "positive")
(incf x)))
The macro call is replaced by this IF+PROGN form before runtime.
At runtime, only the expansion runs; the macro itself is not involved anymore.
Inspecting macro expansion
Common Lisp provides MACROEXPAND-1 and MACROEXPAND to see what your macro turns into.
(macroexpand-1 '(my-when (> x 10)
(print "positive")
(incf x)))
;; => (IF (> X 10)
;; (PROGN (PRINT "positive") (INCF X)))
;; T
This is extremely useful when you're debugging macros: always ask "What code am I actually generating?"
More advanced examples
Example 1: macro with keyword options trailing after body.
(defmacro echo-times (&body body &key (times 1) (prefix "=> "))
;; Remove trailing keyword/value pairs from BODY so we only keep real forms.
(let* ((rev (reverse body))
(pure-rev
(loop with lst = rev
;; While the reversed list starts with a keyword/value pair,
;; drop those two cells.
while (and (consp lst) (consp (cdr lst)) (keywordp (car lst)))
do (setf lst (cddr lst))
finally (return lst)))
(pure-body (reverse pure-rev)))
`(dotimes (i ,times)
(format t "~A" ,prefix)
(progn ,@pure-body))))
(echo-times
(format t "Hello~%")
(format t "World~%")
:times 2
:prefix "[*] ")
Here the macro:
Treats body as a mixture of real forms and trailing keyword-value pairs,
Strips the keyword options off the end,
Then generates a loop (dotimes) that runs the body multiple times with a given prefix.
Example 2: macro with a keyword argument list grouped in one parameter.
(defmacro with-prefix ((&key (prefix "=> ") (times 1)) &body body)
`(dotimes (i ,times)
(format t "~A" ,prefix)
(progn ,@body)))
(with-prefix (:prefix "[*] " :times 2)
(format t "Hello~%")
(format t "World~%"))
This shows how macros can pattern-match argument structure and give a nicer surface syntax, like a mini DSL.
Macro vs function
Aspect
Function
Macro
Arguments
Evaluated before call
Passed as raw code (unevaluated forms)
Runs at
Runtime
Compile-time / macro-expansion-time
Returns
Final value
A form (code) that will later be evaluated
Use when
You just need computation
You need to change syntax or control evaluation
Rule of thumb: use a function unless you specifically need a macro (e.g., to control evaluation, introduce new syntax, or avoid repeated evaluation of arguments).
Common pitfalls: variable capture and GENSYM
When a macro introduces new variable names, there is a risk of clashing with variables in the caller's code.
To avoid this, macros often use gensym to create unique, uninterned symbols.
gensym ensures that the internal variable name won't accidentally shadow or be shadowed by user variables.
This topic is often referred to as macro hygiene.
The declaration part of DEFMACRO
Between the doc-string and the body, you can place declarations:
(defmacro example (x)
"Example macro with declaration."
(declare (ignorable x)) ; tell the compiler it's okay if X is unused
`(list ,x))
Declarations are hints to the compiler (optimization, type information, etc.).
More advanced declaration usage in macros can be explored later.
Summary
A macro is a code transformer: it takes forms as input and returns a new form.
Macros run at expansion time; the expanded code runs at runtime.
Backquote (`), comma (,), and comma-at (,@) are essential tools for building expansion forms.
Use MACROEXPAND-1 to inspect what your macro generates.
Prefer functions unless you really need macro power: control of evaluation, new syntactic constructs, or embedding DSL-like patterns.
Be careful with variable capture; use gensym or appropriate patterns to avoid name clashes.
Destructured Keyword and Parameter Lists in Macros: Clean DSL Design
Introduction
Common Lisp macros can use destructuring parameter lists: a feature that allows you to directly unpack structured arguments.
This means you can write macros that feel like mini-DSLs ("domain-specific languages") with readable, keyword-rich syntax, instead of manually parsing &body or &rest lists.
You can mix and match required parameters, optional parameters, and keyword arguments: even nested within one destructured list.
Why destructured parameters?
In simple macros, the whole argument list is often captured via &body:
&optional (uppercase nil) → toggles extra behavior if supplied.
&body body → code to execute under this style context.
Example usage
(styled-print "Hello Lisp!"
(:prefix "[*] " :times 2 :color "cyan")
t
(format t "Nested body here.~%"))
;; Output:
;; [*] [cyan] HELLO LISP!
;; Nested body here.
;; [*] [cyan] HELLO LISP!
;; Nested body here.
The macro automatically binds all keyword arguments and optional flags.
No list traversal or parsing required: the argument structure itself defines the behavior.
What happens internally
(macroexpand-1
'(styled-print "Hi"
(:prefix "[*] " :color "red" :times 2)
nil
(format t "Body~%")))
;; =>
;; (DOTIMES (I 2)
;; (FORMAT T "~%~A[~A] " "[*] " "red")
;; (FORMAT T "~A~%" "Hi")
;; (PROGN (FORMAT T "Body~%")))
Lisp directly expands the destructured parameters into bindings, just as if you had written them by hand.
Mixing parameter types more generally
You can also use this structure to build macros that accept:
Required arguments (always present),
Optional arguments (default values if omitted),
Keyword groups for configuration,
And a body of forms to execute.
(defmacro do-labeled-range ((start end &key (step 1) (label "Range:"))
&optional (prefix "=> ")
&body body)
"Iterate from START to END printing LABEL and executing BODY each step."
`(do ((i ,start (+ i ,step)))
((> i ,end) 'done)
(format t "~A ~A ~A~%" ,prefix ,label i)
(progn ,@body)))
(start end &key ...) destructures the first argument list directly: like (do-labeled-range (1 10 :step 2)).
&optional prefix defines a default optional prefix string.
The body can contain any Lisp forms to be run on each iteration.
Usage example
(do-labeled-range (1 5 :step 2 :label "Odd numbers:")
"[*] "
(format t "i squared = ~A~%" (* i i)))
;; Output:
;; [*] Odd numbers: 1
;; i squared = 1
;; [*] Odd numbers: 3
;; i squared = 9
;; [*] Odd numbers: 5
;; i squared = 25
;; => DONE
This shows how destructuring can be combined across multiple parameter types to build powerful, readable macro syntaxes.
Why this design is powerful
You get all the flexibility of DEFUN-style argument lists, but applied to code-generation time.
No need for parsing logic: argument structure alone defines how data is bound.
It's an idiomatic Lisp technique for defining small, declarative DSLs (domain-specific sublanguages) that resemble natural configuration blocks.
This style is used by many standard macros:
WITH-OPEN-FILE: destructures file and mode.
WITH-SLOTS: destructures object slots.
DO-SYMBOLS, DO-EXTERNAL-SYMBOLS: destructure parameters with optional parts.
'(a b c) VS (list a b c)
They are NOT the same thing!
'(...) is a quoted list (no evaluation).
(list a b c) calls the function list, after evaluating of each argument.
Comparison with a real-life example:
(setq a 10 b 20 c 30)
(list a b c) ;; (10 20 30)
'(a b c) ;; (A B C)
PROGN
PROGN is a special operator in Common Lisp that evaluates multiple expressions in sequence and returns the value of the last one.
Syntax: (progn <expr1> <expr2> ... <exprN>)
Example:
(progn
(format t "Step 1~%")
(format t "Step 2~%")
(+ 2 3)) ;; last expression
;; Output:
;; Step 1
;; Step 2
;; 5
WHEN
WHEN is a simplified IF that only has a then branch (no else).
It checks a condition, and if the result is true (non-NIL), it evaluates one or more expressions in sequence.
The return value of WHEN is the result of the last expression in its body, if the condition is false, it returns NIL.
(let ((x 5))
(when (> x 0)
(format t "x is positive~%")
(* x 2)))
;; prints: x is positive
;; returns: 10
Equivalent form:WHEN is essentially shorthand for an IF with an implicit PROGN inside its then branch.
(if (> x 0)
(progn
(format t "x is positive~%")
(* x 2)))
The opposite of WHEN is UNLESS, which executes its body when the condition is false.
UNLESS
UNLESS is the logical opposite of WHEN.
It evaluates its body only if the test condition is NIL (false).
The return value of UNLESS is the result of the last expression in its body, if the condition is true, it returns NIL without executing the body.
(let ((x 0))
(unless (> x 0)
(format t "x is not positive~%")
"Did something"))
;; prints: x is not positive
;; returns: "Did something"
Equivalent form:UNLESS is simply shorthand for an IF where the condition is negated and the else branch is omitted.
(if (not (> x 0))
(progn
(format t "x is not positive~%")
"Did something"))
AND, OR, and NOT
AND evaluates the arguments from left to right. (and <expr1> <expr2> ... <exprN>)
It returns:
The first falsy (NIL) value it encounters.
Or the last value if all are true.
OR evaluates the arguments from left to right. (or <expr1> <expr2> ... <exprN>)
It returns:
The first non-falsy (non-NIL) value it encounters.
Or NIL if all are false.
NOT returns T if the argument is NIL, and NIL otherwise.
Examples:
(and t 42 "hello") ;; "hello"
(and t nil "oops") ;; NIL
(and) ;; T
(or nil 0 "yes") ;; 0
(or nil nil nil) ;; NIL
(or) ;; NIL
(not nil) ;; T
(not t) ;; NIL
(not 42) ;; NIL (42 is truthy)
Basics on DOLIST
Purpose
DOLIST is a simple looping construct used to iterate over each element of a list.
It binds a loop variable to each successive element of the list and executes the body for each iteration.
It also supports an optional result-form that determines the final return value after the loop completes.
init: initial value, evaluated once before the loop begins.
step: optional; expression used to update the variable after each iteration.
If omitted, the variable keeps its last value.
end-test: condition evaluated at the start of each iteration.
If true, the loop terminates and evaluates result-form.
result-form: optional; determines what the loop returns.
If omitted, NIL is returned by default.
body: one or more forms evaluated on each iteration until the end-test succeeds.
Evaluation order
All init forms are evaluated first, once, from left to right.
All variables are bound to their init values.
Before each iteration:
end-test is evaluated.
If true → loop stops, returning result-form (or NIL if omitted).
If false → executes the body.
After each iteration:
Each step expression (if any) is evaluated and assigned to its corresponding variable.
Then control goes back to the start, repeating the process.
Basic Example
(do ((i 0 (+ i 1))) ; i starts at 0, increases by 1
((>= i 5) 'done) ; stop when i >= 5, return 'done
(format t "i = ~A~%" i))
;; i = 0
;; i = 1
;; i = 2
;; i = 3
;; i = 4
;; => DONE
i starts at 0.
The loop prints the current value of i each time.
After each iteration, (+ i 1) is computed to update i.
When (>= i 5) becomes true, the loop ends and returns 'done.
Multiple iteration variables
(do ((i 0 (+ i 1)) ; i increments
(j 10 (- j 2))) ; j decrements
((> i 3) (list i j)) ; stop when i > 3, return (i j)
(format t "i=~A, j=~A~%" i j))
;; i=0, j=10
;; i=1, j=8
;; i=2, j=6
;; i=3, j=4
;; => (4 2)
Both i and j update in parallel each iteration.
At the end, both values are available for the result-form.
Accumulating results
(do ((i 1 (+ i 1))
(sum 0 (+ sum i)))
((> i 5) sum))
;; => 15
This loop adds i from 1 to 5 into sum.
When i exceeds 5, the loop stops and returns the accumulated sum.
Creating infinite loops
(do ((x 5)) ; x is initialized once, never updated
((<= x 0)) ; condition never becomes true
(print x))
Because there's no step form, x never changes.
The condition (<= x 0) remains false forever: resulting in an infinite loop.
Use (return ...) to manually break out when needed.
Early exit using RETURN
(do ((i 0 (+ i 1)))
(nil) ; end-test = NIL means infinite unless we RETURN
(when (= i 3)
(return 'early)) ; break manually
(print i))
;; 0
;; 1
;; 2
;; => EARLY
Since the end-test is NIL, the loop would never end by itself.
RETURN exits immediately and provides a custom result.
Combining DO with complex updates
(do ((x 1 (* x 2)) ; doubles each time
(y 10 (- y 3)) ; decreases by 3
(acc '() (cons (* x y) acc))) ; collect products
((or (> x 10) (< y 0)) (nreverse acc))
(format t "x=~A, y=~A~%" x y))
;; x=1, y=10
;; x=2, y=7
;; x=4, y=4
;; x=8, y=1
;; => (10 14 16 8)
This demonstrates three variables evolving in parallel with different stepping rules.
Once either condition triggers, (or (> x 10) (< y 0)), the loop ends and returns the list of products.
DO vs DOTIMES vs DOLIST
Feature
DOLIST
DOTIMES
DO
Purpose
Iterate over list elements
Fixed number of times
Fully manual control (multi-variable)
End condition
End of list
Counter reaches limit
Custom logical test
Return value
Optional result-form
Optional result-form
Optional result-form
Updates
Automatic (next element)
Automatic (i++)
Manual via step expressions
Multiple variables
No
No
Yes
Use case
Traverse lists
Simple numeric loops
Parallel iteration, accumulations, complex logic
LOOP
LOOP is a macro in Common Lisp that provides a declarative, English-like syntax for performing iterations and collecting results.
Basic syntax: (loop [<loop-clauses>])
A loop is composed of clauses like:
Iteration: for, from, to, downto, below, above, by
NOTE: LOOP syntax is non-Lispy: it doesn't follow S-expression style.
Examples:
(loop for x in '(a b c ) do
(print x))
;; A
;; B
;; C
(loop as i from 1 to 3 do (print i))
;; 1
;; 2
;; 3
(loop for i from 1 to 3 do (print i)) ;; 1 2 3
(loop for i from 3 downto 1 do (print i)) ;; 3 2 1
(loop for i from 1 upto 3 do (print i)) ;; 1 2 3
(loop for i from 1 below 4 do (print i)) ;; 1 2 3
(loop for i from 5 above 2 do (print i)) ;; 5 4 3
(loop for i from 0 to 10 by 2 do (print i)) ;; 0 2 4 6 8 10
(loop repeat 3 do
(print "Hello"))
;; "Hello"
;; "Hello"
;; "Hello"
(loop named my-loop
for i from 1 to 10 do
(when (= i 5) (return-from my-loop 'stopped)))
;; 'STOPPED
(loop
initially (format t "Start~%")
for i from 1 to 2 do (print i))
(loop
for i from 1 to 2 do (print i)
finally (format t "Done~%"))
(loop for x in '(a b c) collect x) ;; (A B C)
(loop for tail on '(1 2 3) collect tail) ;; ((1 2 3) (2 3) (3))
(loop for x across #(10 20 30) collect (* x 2)) ;; (20 40 60)
(loop
for i from 1 to 3
if (evenp i)
collect i
else
collect (- i)
end)
;; (-1 2 -3)
(loop for i from 1 to 3 collect (* i i)) ;; (1 4 9)
(loop for x in '((a b) (c) (d e)) append x) ;; (A B C D E)
(loop for i from 1 to 5 sum i) ;; 15
(loop for i in '(1 2 3 4 5) count (evenp i)) ;; 2
(loop for x in '(3 7 2 9 5) maximize x) ;; 9
(loop for x in '(3 7 2 9 5) minimize x) ;; 2
Quote vs. Backquote
Quote (' or (quote ...)): Don't evaluate, take it as data (prevents the usual evaluation of lists/symbols.).
'foo ;; FOO ; a symbol, not the value of variable FOO
(quote foo) ;; same as above
'(1 2 3) ;; (1 2 3) ; a literal list (don't evaluate it)
'(+ 1 2) ;; (+ 1 2) ; code-as-data, not 3
Backquote / Quasiquote (` or (quasiquote ...)): Make a template, selectively evaluate with commas.
,expr (unquote): evaluates expr and inserts the result.
,@expr (unquote-splicing): evaluates expr and splices its elements into the surrounding list.
(let ((a 10) (b '(x y)))
`(+ ,a 20) ;; (+ 10 20) NOTE: it evals a template to a list (not 30 that no further evaluation by far!).
`(1 2 ,a ,@b 99)) ;; (1 2 10 X Y 99)
(let ((xs '(a b)))
`(x ,xs y)) ;; (X (A B) Y)
`(x ,@xs y)) ;; (X A B Y)
Destructing Parameter in DEFMACRO
When you define a macro, you tell Lisp how to map the call form into symbols that you can use inside the macro body.
(defmacro simple (x y)
`(list ,x ,y))
(simple 10 20)
;; expands to (LIST 10 20) and will be evaluated when using it.
In a DEFMACRO,
your lambda list doesn't just bind symbols to arguments: it can pattern-match the entire shape of the call form.
All in all, what I understood it, in the language I could understand, is that:
normally because &body acts like &rest,
so this caused the body would very frequently also include the leter key parameters if given,
using this pattern-matching, we can promote the key parameters before the body,
so it very much eases our work to remove the key parameters from the macro call.
Collections
Vectors are Common Lisp's basic integer-indexed collection, and they come in two flavors: fixed-size vectors and resizable vectors.
Simplest way to construct a fixed-size vector with existing elements,
is to use either (vector [<elements>]) or #(<elements>) syntax:
(vector)
#()
(vector 1)
#(1)
(vector 1 2)
#(1 2)
MAKE-ARRAY is much more vesatile;
(make-array 5 :initial-element nil); #(NIL NIL NIL NIL NIL)
;; `fill-pointer` stores the number of elements inside the vector.
(make-array 5 :fill-pointer 0); #()
(defparameter *x* (make-array 5 :fill-pointer 0))
(vector-push 'a *x*); 0
*x* ; #(A)
(vector-push 'b *x*); 1
*x* ; #(A B)
(vector-push 'c *x*); 2
*x* ; #(A B C)
(vector-pop *x*) ; C
*x* ; #(A B)
(vector-pop *x*) ; B
*x* ; #(A)
(vector-pop *x*) ; A
*x* ; #()
;; To make an arbitrarily resizable vector, you need to pass MAKE-ARRAY another keyword argument: `:adjustable`.
(make-array 5 :fill-pointer 0 :adjustable t); #()
;; You can also specify that a vector only contains certain types of elements (if non-specified, then it could take different kinds of elements).
(make-array 5 :fill-pointer 0 :adjustable t :element-type 'character)
Two-argument function used to compare item (or value extracted by :key function) to element.
EQL
:key
One-argument function to extract key value from actual sequence element. NIL means use element as is.
NIL
:start
Starting index (inclusive) of subsequence.
0
:end
Ending index (exclusive) of subsequence. NIL indicates end of sequence.
NIL
:from-end
If true, the sequence will be traversed in reverse order, from end to start.
NIL
:count
Number indicating the number of elements to remove or substitute or NIL to indicate all (REMOVE and SUBSTITUTE only).
NIL
CONS
A cons cell is the fundamental 2-slot building block of Lisp lists.
Slot1: CAR (the "first" thing)
Slot2: CDR (the "rest" / tail pointer)
(cons a d) allocates a new cons cell whose CAR is a and CDR is d.
(cons 1 2) ; (1 . 2) ;; an *improper* (dotted) pair
(cons 1 '(2 3)) ; (1 2 3) ;; a proper list
(cons 'a 'b) ; (A . B)
(cons '(a b) '(c d)); ((A B) C D)
Proper vs. improper lists:
Proper list: chain of cons cells ending in NIL.
(1 2 3) is shorthand for (1 . (2 . (3 . NIL))).
Improper list: CDR ends with a non-NIL atom.
(1 . 2) or (1 2 . 3).
Basic access: CAR and CDR
(car '(10 20 30)) ; 10
(cdr '(10 20 30)) ; (20 30)
(first '(10 20)) ; 10 ; synonyms: FIRST = CAR, REST = CDR
(rest '(10 20)) ; (20)
(cadr '(a b c)) ; B ; = (car (cdr ...))
(caddr '(a b c d)); C
(cons 0 '(1 2 3)) ; (0 1 2 3) ; O(1) prepend
(list 1 2 3) ; (1 2 3) ; constructs a fresh proper list
(list* 1 2 3) ; (1 2 . 3) ; last arg becomes the final CDR (can make improper)
(append '(1 2) '(3 4)) ; (1 2 3 4) ; copies all but last list; O(n) in first arg
(let ((x (list 1 2 3)))
(setf (car x) 99) ; x => (99 2 3)
(setf (cdr x) '(7)) ; x => (99 7)
x)
SET evaluates its first argument and its result must be a symbol. It only modifies the symbol's global/special value, not lexicals.
Because it is a Function. Functions in Lisp evaluate all their arguments before the function is called.
(set 'a 100)
a
;; 100
(let ((a 1))
(set 'a 200)) ; Sets GLOBAL A, not this lexical A
a ; global A => 200
(let ((x 10))
(set x 20)) ; ERROR: X holds 10, not a symbol
;; Global A
(set 'a 100) ; sets global A = 100
(setq a 'b) ; a now holds the symbol B
(set a 100) ; sets global B = 100
(setq a 42) ; replace `setq` here with `setf` is also ok.
(set a 100) ; ERROR: 42 is not a symbol
(let ((a 1))
(set 'a 999) ; sets global A, lexical a still 1
a) ; 1
Arrays (like in C / JS / TS)
In Common Lisp, an array is a fixed-size, indexable collection of elements.
It is similar to:
In Lisp, vectors are just one-dimensional arrays. Here we focus on the general array features.
Creating 1D Arrays with MAKE-ARRAY:
;; A 1D array of length 5, elements are unspecified, in SBCL it is treated as 0.
(defparameter *a* (make-array 5))
;; A 1D array of length 5, all elements start as 0
;; (defparameter *b* (make-array '(5) :initial-element 0))
(defparameter *b* (make-array 5 :initial-element 0))
*a* ; => #(<UNSPECIFIED> <UNSPECIFIED> <UNSPECIFIED> <UNSPECIFIED> <UNSPECIFIED>)
*b* ; => #(0 0 0 0 0)
Array literal syntax for 1D arrays (vectors):
#(1 2 3) ; vector of 3 elements
#("foo" "bar") ; vector of strings
Accessing and modifying elements with AREF:
(defparameter *nums* (make-array 4 :initial-element 0))
; *NUMS* => #(0 0 0 0)
(aref *nums* 0) ; => 0
(setf (aref *nums* 0) 42)
(aref *nums* 0) ; => 42
(aref *nums* 3) ; last valid index (0-based)
; (aref *nums* 4) ; ERROR: index out of bounds
Element type does not change the API (you still use aref and setf),
but can allow the implementation to store data more compactly (for example, like a C int[] or char[]).
Adjustable Arrays and "Dynamic" Behavior
JavaScript / TypeScript arrays are dynamic: you can always push / pop and change their length.
Common Lisp arrays are normally fixed-size, more like C arrays.
However, you can ask Lisp to make an array:
adjustable → it can be ADJUST-ARRAY-ed to a new size.
with a fill pointer → it has a logical length that can be smaller than the physical capacity.
This combination behaves very similarly to a dynamic array with capacity + current length.
What is a fill pointer?
An array created with :fill-pointer has:
a physical size (capacity) → how many slots are allocated in memory;
a fill pointer → how many elements are considered "in use".
(length array) on such an array returns the fill pointer, not the physical size.
Printing the array also shows only the part up to the fill pointer.
You can still read/write elements beyond the fill pointer with aref, but they are "hidden" until you move the fill pointer forward.
Every Common Lisp array has an element type: this tells Lisp what kinds of values may be stored in it.
If you don't specify one, the default is T (anything).
That means the array can hold numbers, strings, symbols, lists, other arrays: anything at all.
However, when you restrict the element type, Lisp can often use a more compact and efficient internal representation:
similar to how C arrays of int or float are faster and smaller than generic pointer arrays.
Common element types include:
Element Type
Description
Example
T
Can hold any Lisp object (default). Slowest and most general.
(make-array 3 :element-type 't :initial-contents '(a b c))
BIT
Only 0 or 1 allowed. Stored as packed bits, like a C bool[].
Lists are chains of cons cells where each CDR points to the next.
(A . B) is a pair; (A B C) is shorthand for nested CONS ending with NIL.
CAR and CDR retrieve the left and right parts of a cons cell.
PUSH and POP macros are built upon CONS.
Everything in Lisp lists: trees, stacks, queues, association tables: is ultimately built from CONS cells.
Using Symbols in Common Lisp
What is a symbol really?
A symbol is an object with (conceptually) 4 main slots:
NAME: how it prints, e.g. "FOO"
VALUE: used when the symbol is evaluated as a variable
FUNCTION: used when it is called as a function
PLIST: a property list attached to this symbol
Plus: each symbol belongs to a package (a namespace), like COMMON-LISP or CL-USER.
Using symbols as variable names
When you write (setq x 10) or (let ((x 10)) ...),
the symbol X is used as a variable name.
(defvar *counter* 0) ; *COUNTER* is a symbol used as a global variable
(setq *counter* 10) ; store 10 in its value cell
(let ((x 42)) ; X is a symbol used as a local variable
x) ; => 42
Evaluating a symbol as an expression uses its value cell:
(setq a 100)
a ; => 100 ; evaluates to the value cell of A
(symbol-value 'a) ; => 100 ; same, but using the symbol object explicitly
Using symbols as function names
When you define a function with DEFUN, the symbol's function cell is filled.
(defun square (x)
(* x x))
(square 5) ; => 25
(function square) ; => #<FUNCTION SQUARE>
(symbol-function 'square) ; same as FUNCTION but as a function
Evaluating (square 5):
First position: square is treated as a function name.
Lisp looks up the function cell of the symbol SQUARE.
Then calls it with argument 5.
Symbols as data (quoted)
If you write a symbol quoted, Lisp does not treat it as a variable or function: it is just data.
'foo ; symbol FOO
(quote foo); same
'(a b c) ; list of three symbols: A, B, C
This is the core of "code as data": Lisp programs are made of symbols and lists, and you can manipulate them directly.
(defparameter *expr* '(+ 1 2))
*expr* ; => (+ 1 2) ; data (list of symbols and numbers)
(eval *expr*) ; => 3 ; treat it as code and evaluate
Symbols as keys in plists and alists
Symbols are often used as keys because they are easy to read and efficiently comparable.
To convert a symbol back to a string: (symbol-name 'my-app::foo) → "FOO".
To create a symbol from a string: (intern "FOO" "MY-APP").
Exporting and importing symbols
To make a symbol "public" from a package, call EXPORT.
(in-package :my-app)
(defvar *secret* 42)
(export '*secret*)
(symbol-package '*secret*) ; => #<PACKAGE "MY-APP">
;; From another package / REPL:
MY-APP:*SECRET* ; use single colon for exported symbol
To "use" exported symbols from another package without writing the prefix every time, use USE-PACKAGE:
(use-package :my-app)
*secret* ; now accessible directly (if no name conflict)
Summary
A package is a namespace that owns symbols and controls visibility.
MAKE-PACKAGE creates a package; DEFPACKAGE is the usual, declarative way.
INTERN creates/finds a symbol in a package from a string.
FIND-SYMBOL only finds, never creates.
pkg:foo refers to an exported symbol; pkg::foo refers to an internal one.
IN-PACKAGE switches the "current" package for reading and interning symbols.
EXPORT and USE-PACKAGE control what becomes your public API.
my-app:start-app VS my-app::start-app
Recap: exported vs internal symbols
When you define a package with DEFPACKAGE, you can choose which symbols are exported (public) and which remain internal (private).
Here, start-app and *version* are exported from MY-APP.
Other symbols you define but don't list in :export stay internal.
Single colon: pkg:symbol -> exported only
pkg:symbol means: "Use the exported symbol named SYMBOL from package PKG."
If that symbol isn't exported, you get a reader error when loading/compiling code.
Example (assuming start-app is not exported):
;; Suppose:
(defpackage :my-app
(:use :cl)) ; no :export here
(in-package :my-app)
(defun start-app ()
(format t "Starting app!~%"))
;; From CL-USER:
CL-USER> (my-app:start-app)
;; => Reader error:
;; The symbol "START-APP" is not exported from package "MY-APP".
So: one colon works only for exported (public) API symbols.
Double colon: pkg::symbol → internal or external
pkg::symbol means: "Give me the symbol named SYMBOL in package PKG, even if it's internal."
It can see both:
exported symbols
internal (non-exported) symbols
Continuing the same example (no :export):
CL-USER> (my-app::start-app)
Starting app!
NIL
my-app::start-app works even though start-app was not exported.
This is how you can "peek inside" another package's internal implementation.
What if the symbol doesn't exist?
If you typo the name with pkg::typo-name, the reader will intern a new symbol in that package:
CL-USER> 'my-app::start-ap
;; => MY-APP::START-AP ; a new symbol was created
CL-USER> (my-app::start-ap)
;; => Undefined-function error at runtime
So :: will not throw a reader error: it either finds the symbol or creates a new internal one.
If it's new and you try to call it as a function, you'll get a normal undefined function runtime error.
Export vs internal access: behavior table
Symbol status in MY-APP
Form
Result
Exported
my-app:start-app
✅ Works
Exported
my-app::start-app
✅ Also works (can still use internal-access syntax)
Internal (not exported)
my-app:start-app
❌ Reader error: not exported
Internal (not exported)
my-app::start-app
✅ Works (internal symbol access)
Best practice / style
Use one colon (pkg:foo) when you are calling the public API of another package.
Use two colons (pkg::foo) only when:
You're debugging, testing, or exploring.
You intentionally want to reach into private internals (with the risk that they may change).
Library authors should clearly :export only the symbols they want others to rely on.
Summary
If you don't :exportstart-app from MY-APP:
my-app:start-app → error (not exported).
my-app::start-app → works, because it can access internal symbols.
Single colon = "public, exported symbol only".
Double colon = "any symbol (internal or external) with that name in the package."
This is handy when you want to store or iterate over returned values.
MULTIPLE-VALUE-CALL
multiple-value-call applies a function to all values produced by its arguments.
(multiple-value-call #'list (values 'a 'b 'c))
;; => (A B C)
It's like calling (apply #'list '(a b c)), but directly supports multiple-value returns.
Ignoring extra values
When a function returns multiple values but you only care about one, Lisp automatically ignores the rest.
(defun test ()
(values 1 2 3))
(+ (test) 5)
;; => 6 ; only first value 1 used
This makes multiple-value functions backward compatible with single-value code.
VALUES-LIST
The opposite of multiple-value-list: it turns a list into multiple values.
(values-list '(10 20 30))
;; => 10, 20, 30
Used internally by macros that forward unknown numbers of return values.
Key Functions and Macros Summary
Name
Purpose
Example
values
Return multiple values
(values a b c)
multiple-value-bind
Bind multiple results to variables
(multiple-value-bind (x y) (func) ...)
multiple-value-list
Collect values into a list
(multiple-value-list (func))
values-list
Return list elements as multiple values
(values-list '(1 2 3))
multiple-value-call
Call function with all returned values
(multiple-value-call #'list (values 1 2 3))
Comparison to lists and tuples
Concept
Multiple Values
List
Representation
Native runtime feature (not a list)
Explicit object
Memory allocation
No consing needed
Creates a list object
Efficiency
Very fast
Slower for temporary data
Backward compatibility
Only first value used if context ignores others
Always one object
Currying vs Closures
Closures: Functions that remember
A closure is a function that "remembers" variables from the environment where it was created.
Even after that environment is gone, the function retains access to those variables: they are closed over.
(defun make-adder (n)
"Return a function that adds N to its argument."
#'(lambda (x)
(+ x n)))
(setq add5 (make-adder 5))
(funcall add5 10) ; => 15
(funcall add5 100) ; => 105
Here make-adder returns a function that captures the variable n.
Each call to make-adder creates a new closure with its own private n.
WRITE: the most general printing function, with many keyword options to control behavior.
It can specify whether to print readably, add escape characters, or include structure indentation.
FORMAT: the most powerful printing utility, allowing formatted text output (similar to printf in C).
It can print to the terminal or to a string, and supports rich directives starting with ~.
Its basic signature is:
(format <destination> <control-string> &rest <arguments>)
It interprets the control string and replaces ~-directives with formatted versions of the given arguments.
Destination argument:
The first argument (destination) determines where the output goes:
T: print to the standard output (usually the terminal).
NIL: do not print, instead, return the resulting string.
a stream: print to that stream (e.g. *standard-output*, file stream, string stream).
(format t "Hello, ~a!~%" "World")
;; prints: Hello, World!
;; returns: NIL
(format nil "Hello, ~a!~%" "World")
;; prints nothing
;; returns: "Hello, World!
;; " (with a newline at the end)
(format nil "Hello, ~a!" "World")
;; prints nothing
;; returns: "Hello, World!"
(with-open-file (out "greeting.txt"
:direction :output
:if-exists :supersede)
(format out "Saved greeting: ~a~%" "Hello from Common Lisp"))
;; writes to the file greeting.txt
Basic substitution directives:
~a — aesthetic printing (user-friendly, similar to princ).
~s — standard printing (reader-friendly, similar to prin1).
~d — print an integer in decimal.
~% — print a newline.
~~ — print a literal tilde (~).
(format t "Aesthetic (~a) vs standard (~s)~%" "Hello" "Hello")
;; prints: Aesthetic (Hello) vs standard ("Hello")
(format t "Decimal: ~d~%" 42)
;; prints: Decimal: 42
(format t "Line 1~%Line 2~%")
;; prints:
;; Line 1
;; Line 2
(format t "Tilde example: ~~ This is a tilde.~%")
;; prints: Tilde example: ~ This is a tilde.
~a is meant for human-readable output, ~s for Lisp-readable output.
(format t "Using ~~a: ~a~%" "Hello")
;; prints: Using ~a: Hello
(format t "Using ~~s: ~s~%" "Hello")
;; prints: Using ~s: "Hello"
~d is for integers (decimal),
~f is for floating-point numbers, with optional width and precision controls.
(format t "Integer: ~d~%" 123)
;; prints: Integer: 123
(format t "Float default: ~f~%" 3.14159)
;; prints something like: Float default: 3.14159
;; ~,2f → 2 digits after the decimal point
(format t "Pi to 2 decimals: ~,2f~%" 3.14159)
;; prints: Pi to 2 decimals: 3.14
;; ~10,2f → width 10, 2 decimals (padded on the left)
(format t "Right-aligned: |~10,2f|~%" 3.14159)
;; prints: Right-aligned: | 3.14|
Newlines and "fresh-line":
~% → always prints a newline.
~& → prints a newline only if not already at the beginning of a line (like fresh-line).
(format t "First line~%Second line~%")
;; prints:
;; First line
;; Second line
(format t "Hello")
(format t "~&World~%")
;; If "Hello" ended the line, ~& starts a new one.
;; prints:
;; Hello
;; World
;; So basically it does the same as below:
(format t "Hello~%")
(format t "~&World~%")
;; prints:
;; Hello
;; World
Multiple arguments and ordering:
Each directive typically consumes one argument (unless it is a control directive).
The arguments are used in the order they appear.
The ~[ ... ~] control directive chooses among several alternatives based on an integer argument.
The integer argument is used as a zero-based index.
Clauses are separated by ~;.
(format t "~[zero~;one~;two~;many~]~%" 0)
;; prints: zero
;; returns: NIL
(format t "~[zero~;one~;two~;many~]~%" 2)
;; prints: two
;; returns: NIL
(format t "~[zero~;one~;two~;many~]~%" 10)
;; prints nothing in SBCL
;; returns: NIL
;; (However, the last clause is used as "fallback" in some other lisp interpreters.)
The ~{ ... ~} control directive repeats its body for each element in a list argument.
Inside the body, directives consume elements from the current list.
The ~* control directive allows you to skip one or more arguments without printing them.
This is useful when combining FORMAT with conditional constructs.
(format t "~a ~*~a~%" "first" "skip-me" "third")
;; ~a consumes "first"
;; ~* skips "skip-me"
;; ~a consumes "third"
;;
;; prints: first third
;; returns: NIL
The ~< ... ~> control directive can be used to create formatted fields with alignment (left, right, centered).
This is more advanced, but useful for tables and pretty printing.
(format t "~<~100a~>~%" "Hi")
;; A simple example: pad "Hi" with 98 blank spaces on the right into a field.
;; Exact behavior depends on additional modifiers,
;; but ~< ... ~> is the basic block alignment mechanism.
FORMAT to build strings (instead of printing):
As mentioned, using nil as the destination makes FORMAT return a string.
This is very convenient for constructing complex strings.
(let ((msg (format nil "User ~a has ~d unread messages." "Alice" 5)))
(print msg))
;; prints: "User Alice has 5 unread messages."
;; msg is the string: "User Alice has 5 unread messages."