Lisp
The original AI language.
Lisp, created by John McCarthy in 1958, is the second-oldest high-level language still in use, and it was the language of AI research for decades. It introduced garbage collection, recursion as a core tool, and code written as data. This volume uses Common Lisp, the main standard dialect.
Why learn Lisp?
Lisp shaped modern programming and early AI, and its macros still show what a programmable language can be.
- AI heritage: expert systems and early AI research were written in Lisp.
- Code as data: macros let you extend the language itself.
- Interactive: redefine functions while the program runs.
Set up and run your first program
Install SBCL (Steel Bank Common Lisp) and Quicklisp for libraries. Emacs with SLIME or VS Code with Alive make a great environment.
sudo apt install sbcl
sbcl --script hello.lisp(defparameter *name* "Ada")
(format t "Hello, AI! Welcome to Lisp, ~a.~%" *name*)format uses directives: ~a prints a value and ~% prints a newline.S-expressions and values
Code and data share one form: lists in parentheses. The first element is the function. Global variables are named with *earmuffs* by convention.
(defparameter *layers* 32)
(defparameter *model* "llama3.1")
(format t "~a: ~a layers~%" *model* *layers*)
(format t "FP16: ~,1f GB~%" (/ (* 8.03e9 2) 1e9))
(print (list 1 2 3))
(print '(this is data, not code))Functions
defun defines functions. Optional and keyword arguments make flexible interfaces. lambda makes anonymous functions.
(defun params (layers &optional (width 4096))
(* 12 layers width width))
(defun square (x) (* x x))
(print (params 32))
(print (mapcar #'square '(1 2 3 4)))
(print (funcall (lambda (x) (* 2 x)) 21))
(print (reduce #'+ '(80 141 180)))Lists and recursion
Lists are built from cons cells. car gets the first item and cdr the rest; recursion over lists is natural.
(defparameter *vram* '(("H100" . 80) ("H200" . 141) ("RTX 5090" . 32)))
(defun big-gpus (gpus)
(cond ((null gpus) nil)
((>= (cdar gpus) 80) (cons (caar gpus) (big-gpus (cdr gpus))))
(t (big-gpus (cdr gpus)))))
(print (big-gpus *vram*)) ; ("H100" "H200")
(print (cdr (assoc "H200" *vram* :test #'string=)))Control flow and loop
if, when, cond and case make decisions. The loop macro is a mini-language for iteration.
(defun precision (bits)
(case bits (32 "FP32") (16 "FP16") (8 "FP8") (t "other")))
(print (precision 8))
(loop for epoch from 1 to 3 do (format t "epoch ~a~%" epoch))
(print (loop for x in '(1 2 3 4 5) when (evenp x) collect (* x x)))
(print (loop for i from 1 to 10 sum i))Macros: code that writes code
Because code is a list, macros can transform it before it runs. This is how Lisp programmers add new syntax to the language.
(defmacro with-timing (&body body)
`(let ((start (get-internal-real-time)))
(prog1 (progn ,@body)
(format t "took ~a ms~%"
(round (* 1000 (- (get-internal-real-time) start))
internal-time-units-per-second)))))
(with-timing
(loop for i from 1 to 1000000 sum i))Lisp and AI
Lisp was the language of symbolic AI: McCarthy coined the term "artificial intelligence", and expert systems, early natural language programs and Lisp machines all came from this world. Today's deep learning mostly uses Python, but Lisp is still used for symbolic reasoning, planning and research, and it can call LLM APIs with libraries like Dexador.
The essential libraries
A tiny symbolic rule engine
Classic symbolic AI: facts plus if-then rules, applied until nothing new is learned.
(defparameter *facts* '((gpu h100) (memory h100 80)))
(defparameter *rules*
'(((gpu ?x) (memory ?x 80)) . (datacenter ?x)))
(defun infer ()
(dolist (f *facts*)
(when (eq (first f) 'gpu)
(let ((x (second f)))
(when (member (list 'memory x 80) *facts* :test #'equal)
(pushnew (list 'datacenter x) *facts* :test #'equal)))))
*facts*)
(print (infer)) ; adds (DATACENTER H100)Build an AI agent in Lisp
With Dexador for HTTP and jzon for JSON (load both with Quicklisp), Common Lisp runs the same tool-calling loop as the other volumes.
(ql:quickload '(:dexador :com.inuoe.jzon))
(defpackage :agent (:use :cl) (:local-nicknames (:jzon :com.inuoe.jzon)))
(in-package :agent)
(defparameter *url* "http://localhost:11434/v1/chat/completions")
(defparameter *tools* (jzon:parse "[{\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"description\":\"Weather for a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"]}}}]"))
(defun obj (&rest kv)
(let ((h (make-hash-table :test #'equal)))
(loop for (k v) on kv by #'cddr do (setf (gethash k h) v)) h))
(defun chat (messages)
(let* ((body (jzon:stringify (obj "model" "llama3.1" "messages" (coerce messages 'vector) "tools" *tools*)))
(res (jzon:parse (dex:post *url* :content body :headers '(("Content-Type" . "application/json"))))))
(gethash "message" (aref (gethash "choices" res) 0))))
(defun run (prompt)
(let ((messages (list (obj "role" "user" "content" prompt))))
(loop repeat 5
for msg = (chat messages)
for calls = (gethash "tool_calls" msg)
do (setf messages (append messages (list msg)))
(when (or (null calls) (zerop (length calls)))
(return (gethash "content" msg)))
(loop for c across calls
for args = (jzon:parse (gethash "arguments" (gethash "function" c)))
do (setf messages (append messages
(list (obj "role" "tool" "tool_call_id" (gethash "id" c)
"content" (format nil "It is 24°C and sunny in ~a." (gethash "city" args))))))))))
(print (run "Should I take an umbrella in Lahore today?"))jzon parses JSON objects into hash tables, so fields are read with gethash.
ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.Lisp learning roadmap
Tick items off as you go. Your progress is saved in this browser.
Beginner
Intermediate
Advanced
Projects to build
Lisp cheat sheet
| Task | Syntax |
|---|---|
| Global | (defparameter *x* 5) |
| Function | (defun add (a b) (+ a b)) |
| Lambda | (lambda (x) (* x 2)) |
| Local | (let ((x 1)) ...) |
| Map list | (mapcar #'f list) |
(format t "~a~%" x) |
Common mistakes
- Quoting matters:
'(a b)is data,(a b)calls functiona. - Use
equalorstring=to compare strings;eqcompares identity. - Different dialects (Common Lisp, Scheme, Clojure) look similar but are not compatible.
Where to go next
- Practical Common Lisp — free book
- Common Lisp Cookbook — recipes
- SBCL — compiler
On TensorCore.si: Clojure volume, Scheme volume.
Keyboard: [ previous language · ] next language · / search the library