Перейти к основному содержимому

Справочник по Lisp


Справочник-шпаргалка по Lisp — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс: раздел.

Справочник ориентирован на ANSI Common Lisp. Примеры кода можно копировать в REPL (SBCL, CCL, CLISP). В учебных главах раздела — введение и порядок чтения.


Справочные таблицы​

Содержание справочника​

Быстрый поиск по учебнику: основы · архитектура · типы · управление · функции · первая программа · FP-стиль


Основы синтаксиса и базовые структуры данных​

1. Синтаксис выражений​

Все программы на Lisp записываются в виде S-выражений (symbolic expressions). S-выражение — это либо атом, либо список.

Примеры:

42 ; атом (число)
hello ; атом (символ)
"world" ; атом (строка)
(+ 1 2) ; список: вызов функции +

Выражение всегда вычисляется. Результат вычисления зависит от типа выражения.


2. Атомы и составные объекты​

В Common Lisp атом — всё, что не является cons-ячейкой: (atom x) эквивалентно (not (consp x)). Числа и символы — атомы; строки, векторы, хэш-таблицы — нет ((atom "hi") → NIL).

К элементарным типам данных обычно относят:

  • Числа — целые, рациональные, вещественные, комплексные.
  • Символы: имена переменных, функций; ключевые слова (:keyword) — самоссылочные символы.
  • Строки: массивы символов (character).
  • Логика: T (истина), NIL (ложь и пустой список).

Примеры:

123 ; целое число
3/4 ; рациональное число
3.14 ; вещественное число
#C(1 2) ; комплексное число 1+2i
my-var ; символ
"Hello" ; строка
:debug ; ключевое слово
T ; истина
NIL ; ложь / пустой список

3. Списки​

Список — это упорядоченная последовательность элементов, заключённая в круглые скобки. Список может быть пустым: () или NIL.

Списки строятся из cons-ячеек — пар (car . cdr), где car — первый элемент, cdr — остальная часть списка.

Функции для работы со списками:

  • (cons a b) — создаёт cons-ячейку из a и b.
  • (car list) — возвращает первый элемент списка.
  • (cdr list) — возвращает список без первого элемента.
  • (list a b c ...) — создаёт новый список из аргументов.
  • (append list1 list2 ...) — объединяет списки.
  • (length list) — возвращает количество элементов.
  • (nth n list) — возвращает элемент под индексом n (с нуля).
  • (last list) — возвращает последний cons-элемент списка.
  • (reverse list) — возвращает список в обратном порядке.

Примеры:

(cons 1 '(2 3)) ; → (1 2 3)
(car '(a b c)) ; → A
(cdr '(a b c)) ; → (B C)
(list 1 2 3) ; → (1 2 3)
(append '(1 2) '(3 4)) ; → (1 2 3 4)
(nth 1 '(x y z)) ; → Y

4. Типы данных​

Common Lisp поддерживает богатую систему типов. Основные категории:


4.1. Числовые типы​
  • integer — целые числа (неограниченной точности).
  • ratio — рациональные числа (дроби, например 2/3).
  • float — вещественные числа (одинарной, двойной и других точностей).
  • complex — комплексные числа.

Функции:

  • (numberp x) — проверяет, является ли x числом.
  • (integerp x), (floatp x), (ratiop x), (complexp x) — проверки конкретных числовых типов.
  • (zerop x), (plusp x), (minusp x) — проверки знака.
  • (evenp x), (oddp x) — чётность/нечётность.

4.2. Символы​

Символ — именованная сущность, которая может иметь:

  • имя,
  • значение (если используется как переменная),
  • функциональное определение (если используется как функция),
  • свойства (plist).

Функции:

  • (symbolp x) — проверка, является ли x символом.
  • (symbol-name sym) — имя символа как строка.
  • (boundp sym) — проверка, имеет ли символ значение.
  • (fboundp sym) — проверка, связана ли функция с символом.
  • (symbol-value sym) — получение значения символа.
  • (symbol-function sym) — получение функции, связанной с символом.

4.3. Строки​

Строка — массив символов (character).

Функции:

  • (stringp x) — проверка, является ли x строкой.
  • (string= s1 s2) — сравнение строк (регистрозависимо).
  • (string-equal s1 s2) — сравнение без учёта регистра.
  • (length str) — длина строки.
  • (char str i) — символ по индексу i.
  • (subseq str start &optional end) — подстрока.
  • (concatenate 'string s1 s2 ...) — объединение строк.

4.4. Массивы и векторы​
  • Вектор — одномерный массив.
  • Массивы могут быть многомерными.

Функции:

  • (vectorp x) — проверка вектора.
  • (aref array &rest indices) — доступ к элементу.
  • (make-array dimensions &key initial-element element-type fill-pointer adjustable) — создание массива.

4.5. Хэш-таблицы​

Ассоциативные структуры данных.

Функции:

  • (make-hash-table &key test size rehash-size rehash-threshold) — создание.
  • (gethash key table &optional default) — получение значения.
  • (setf (gethash key table) value) — установка значения.
  • (remhash key table) — удаление ключа.
  • (clrhash table) — очистка таблицы.
  • (maphash function table) — применение функции к каждой паре ключ-значение.

4.6. Потоки (streams)​

Используются для ввода/вывода.

Функции:

  • (open filename &key direction if-exists if-does-not-exist) — открытие файла.
  • (close stream) — закрытие.
  • (read stream) — чтение объекта.
  • (write object &key stream) — запись.
  • (print object &optional stream) — печать с новой строки.
  • (format destination control-string &rest args) — форматированный вывод.

Функции, лямбда-выражения, замыкания и рекурсия​

1. Определение функций​

Функции в Common Lisp определяются с помощью формы defun.

Синтаксис:

(defun имя (параметры)
тело...)

Пример:

(defun square (x)
(* x x))

Вызов:

(square 5) ; → 25

Функция возвращает значение последнего вычисленного выражения в теле.


2. Параметры функций​

Common Lisp поддерживает несколько видов параметров:


2.1. Обязательные параметры​

Указываются просто по имени.

(defun add (a b) (+ a b))

2.2. Необязательные параметры (&optional)​

Могут иметь значения по умолчанию.

(defun greet (name &optional (greeting "Hello"))
(format nil "~A, ~A!" greeting name))

Вызовы:

(greet "Alice") ; → "Hello, Alice!"
(greet "Bob" "Hi") ; → "Hi, Bob!"

2.3. Остаточные параметры (&rest)​

Собирают все оставшиеся аргументы в список.

(defun sum (&rest numbers)
(apply #'+ numbers))

Вызов:

(sum 1 2 3 4) ; → 10

2.4. Именованные параметры (&key)​

Аргументы передаются как пары ключ–значение.

(defun make-point (&key (x 0) (y 0))
(list x y))

Вызов:

(make-point :x 10 :y 20) ; → (10 20)
(make-point :y 5) ; → (0 5)

Ключевые параметры могут быть обязательными через &key ((x x-supplied-p)), но чаще используют значения по умолчанию.


2.5. Вспомогательные параметры (&aux)​

Позволяют объявлять локальные переменные внутри списка параметров.

(defun circle-area (r &aux (pi 3.14159))
(* pi r r))

Все виды параметров можно комбинировать в порядке: обязательные &optional необязательные &rest остаток &key ключи &aux вспомогательные

Пример:

(defun complex-fn (a b &optional (c 0) &rest rest &key (debug nil) &aux (sum (+ a b c)))
(when debug (print sum))
(list sum rest))

3. Лямбда-выражения​

Анонимные функции создаются с помощью lambda.

Синтаксис:

(lambda (аргументы) тело...)

Пример:

(funcall (lambda (x) (* x x)) 7) ; → 49

Лямбда-выражения часто используются как аргументы для функций высшего порядка.


4. Функции высшего порядка​

Это функции, принимающие другие функции в качестве аргументов или возвращающие их.

Основные функции:

  • (funcall function &rest args) — вызывает функцию с аргументами.
  • (apply function arg-list) — вызывает функцию, раскрывая список аргументов.
  • (mapcar function list) — применяет функцию к каждому элементу списка, возвращает новый список.
  • (mapcan function list) — как mapcar, но результаты конкатенируются через nconc.
  • (every predicate list) — проверяет, выполняется ли предикат для всех элементов.
  • (some predicate list) — проверяет, выполняется ли предикат хотя бы для одного элемента.
  • (reduce function list &key initial-value) — сворачивает список бинарной операцией.

Примеры:

(mapcar #'square '(1 2 3 4)) ; → (1 4 9 16)
(every #'plusp '(1 2 3)) ; → T
(some #'minusp '(-1 2 3)) ; → T
(reduce #'+ '(1 2 3 4)) ; → 10
(apply #'+ '(1 2 3)) ; → 6
(funcall #'+ 1 2 3) ; → 6

Заметим: #' — это сокращение для (function ...), используется для ссылки на функцию по имени.


5. Замыкания​

Замыкание — функция, захватывающая переменные из окружающей лексической области.

Пример:

(defun make-counter (initial)
(let ((count initial))
(lambda () (incf count))))

Использование:

(defvar counter (make-counter 10))
(funcall counter) ; → 11
(funcall counter) ; → 12

Переменная count сохраняется между вызовами благодаря замыканию.


6. Рекурсия​

Lisp естественно поддерживает рекурсивные функции. Хвостовая рекурсия не гарантирует оптимизацию во всех реализациях, но многие компиляторы её поддерживают.

Пример факториала:

(defun factorial (n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))

Рекурсия с аккумулятором (хвостовая):

(defun factorial-tail (n &optional (acc 1))
(if (<= n 1)
acc
(factorial-tail (- n 1) (* n acc))))

7. Локальные функции​

Форма flet позволяет определять локальные функции, видимые только внутри блока.

Синтаксис:

(flet ((имя (параметры) тело) ...)
основное-тело)

Пример:

(flet ((square (x) (* x x))
(double (x) (* 2 x)))
(+ (square 3) (double 4))) ; → 9 + 8 = 17

Форма labels аналогична flet, но позволяет рекурсивные и взаимно рекурсивные определения, так как тела функций видят друг друга.

Пример взаимной рекурсии:

(labels ((even? (n)
(if (zerop n) t (odd? (- n 1))))
(odd? (n)
(if (zerop n) nil (even? (- n 1)))))
(even? 4)) ; → T

8. Компиляция функций​

Функции могут быть скомпилированы для повышения производительности.

  • (compile 'имя) — компилирует уже определённую функцию.
  • (compile-file "файл.lisp") — компилирует весь файл в .fasl.
  • (load "файл.fasl") — загружает скомпилированный файл.

Форма defun в интерактивной среде обычно создаёт интерпретируемую функцию, но многие реализации (SBCL, CCL) автоматически компилируют.


9. Интроспекция функций​

Common Lisp предоставляет средства для анализа функций:

  • (function-lambda-expression fn) — может вернуть исходное лямбда-выражение (не во всех реализациях).
  • (describe fn) — выводит информацию о функции.
  • (documentation 'имя 'function) — получает строку документации, если она указана.

Пример с документацией:

(defun add (a b)
"Возвращает сумму двух чисел."
(+ a b))

(documentation 'add 'function) ; → "Возвращает сумму двух чисел."

Специальные формы и макросы​

1. Что такое специальные формы​

Специальные формы — это встроенные конструкции языка, которые не вычисляют свои аргументы обычным образом. Они управляют потоком выполнения, привязками переменных, компиляцией и другими фундаментальными аспектами.

Common Lisp имеет небольшой набор специальных форм (всего около 25), но на их основе строятся все остальные управляющие конструкции через макросы.


2. Основные специальные формы​

2.1. quote​

Предотвращает вычисление выражения и возвращает его как данные.

Синтаксис:

(quote x)

Сокращение: 'x

Примеры:

(quote (+ 1 2)) ; → (+ 1 2)
'hello ; → HELLO
'(1 2 3) ; → (1 2 3)

2.2. setq​

Присваивает значение переменной (или нескольким переменным).

Синтаксис:

(setq var1 value1 var2 value2 ...)

Пример:

(setq x 10 y 20)

setq работает с уже существующими переменными. Для глобальных переменных предпочтительно использовать defvar или defparameter.


2.3. let и let*​

Создают локальные привязки переменных.

  • let — все инициализаторы вычисляются в параллельном окружении, то есть не видят друг друга.
  • let* — инициализаторы вычисляются последовательно, каждая последующая переменная видит предыдущие.

Примеры:

(let ((a 1) (b 2))
(+ a b)) ; → 3

(let* ((a 1) (b (+ a 1)))
(+ a b)) ; → 3

Без let* второй пример вызвал бы ошибку, если бы b зависел от a в let.


2.4. if​

Условное ветвление с двумя ветками.

Синтаксис:

(if условие
форма-если-истина
форма-если-ложь)

Ветка "ложь" необязательна.

Пример:

(if (> x 0)
"positive"
"non-positive")

2.5. cond​

Многоусловное ветвление.

Синтаксис:

(cond
(условие1 форма1...)
(условие2 форма2...)
...
(t форма-по-умолчанию...))

Каждая ветка — список, где первое выражение — условие, остальные — тело. Выполняется первая ветка с истинным условием.

Пример:

(cond
((< x 0) "negative")
((= x 0) "zero")
(t "positive"))

2.6. progn​

Группирует несколько форм в одну. Возвращает значение последней формы.

Используется там, где синтаксис допускает только одну форму, но нужно выполнить несколько.

Пример:

(if (> x 0)
(progn
(print "Positive!")
(* x 2)))

2.7. block и return-from​

block задаёт именованную точку выхода. return-from немедленно возвращает значение из блока.

Синтаксис:

(block имя
тело...)

Пример:

(block my-block
(when (< x 0) (return-from my-block "Invalid"))
(* x x))

Анонимный блок создаётся формой prog1, prog2, progn, но именованный даёт гибкость.


2.8. tagbody и go​

Низкоуровневый механизм меток и безусловных переходов.

Пример:

(tagbody
start
(print "Hello")
(go end)
end
(print "Done"))

Редко используется напрямую, чаще служит основой для макросов циклов.


2.9. function​

Ссылается на функциональное определение.

Синтаксис:

(function имя)

Сокращение: #'имя

Пример:

(mapcar #'sqrt '(4 9 16)) ; → (2 3 4)

2.10. eval​

Вычисляет переданное S-выражение как код.

Пример:

(eval '(+ 1 2)) ; → 3

Использовать eval следует крайне редко — он медленный и небезопасный.


2.11. load и compile-file​
  • (load "file") — загружает и выполняет файл.
  • (compile-file "file.lisp") — компилирует файл в машинный код (.fasl).

Эти формы управляют загрузкой кода на этапе выполнения.


3. Макросы​

Макросы — ключевой инструмент метапрограммирования в Lisp. Они преобразуют код на этапе чтения/компиляции, до выполнения.

Макрос получает код как данные, обрабатывает его и возвращает новый код, который затем вычисляется.


3.1. defmacro​

Определяет макрос.

Синтаксис:

(defmacro имя (параметры)
тело...)

Тело должно вернуть S-выражение — сгенерированный код.

Пример простого макроса:

(defmacro when-positive (x &body body)
`(if (> ,x 0)
(progn ,@body)))

Использование:

(when-positive temperature
(print "It's warm!")
(turn-on-fan))

Раскрывается в:

(if (> temperature 0)
(progn
(print "It's warm!")
(turn-on-fan)))

3.2. Кавычки — backquote, comma, comma-at​
  • Backquote ` — как quote, но позволяет интерполяцию.
  • Comma , — подставляет значение выражения.
  • Comma-at ,@ — раскрывает список (splicing).

Пример:

(let ((x 1) (y 2) (rest '(3 4)))
`(list ,x ,y ,@rest)) ; → (list 1 2 3 4)

3.3. Гигиена и gensym​

Чтобы избежать захвата переменных (variable capture), макросы должны использовать уникальные символы.

Функция (gensym) создаёт новый, уникальный символ при каждом вызове.

Пример безопасного макроса:

(defmacro with-gensyms ((&rest vars) &body body)
`(let ,(mapcar (lambda (v) `(,v (gensym))) vars)
,@body))

(defmacro my-let ((var val) &body body)
(with-gensyms (g)
`(let ((,g ,val))
(let ((,var ,g))
,@body))))

Это гарантирует, что внутренняя переменная g не конфликтует с пользовательским кодом.


3.4. Распространённые встроенные макросы​

Common Lisp предоставляет множество макросов, построенных на специальных формах:

  • when — как if без ветки else.
(when (> x 0) (print x))
  • unless — противоположность when.
(unless (zerop x) (print (/ 1 x)))
  • case — множественный выбор по значению.
(case op
(+ (add a b))
(- (sub a b))
(otherwise (error "Unknown op")))
  • multiple-value-bind — привязка нескольких значений.
(multiple-value-bind (quotient remainder)
(floor 10 3)
(list quotient remainder)) ; → (3 1)
  • destructuring-bind — деструктуризация списков или векторов.
(destructuring-bind (x y z) '(1 2 3)
(+ x y z)) ; → 6

3.5. Отладка макросов​
  • (macroexpand-1 form) — раскрывает макрос один уровень.
  • (macroexpand form) — раскрывает рекурсивно, пока возможно.

Пример:

(macroexpand-1 '(when (> x 0) (print x)))
; → (IF (> X 0) (PROGN (PRINT X)))

Полезно для проверки корректности генерации кода.


3.6. Макросы времени компиляции​

Макросы выполняются во время компиляции, поэтому они могут:

  • читать файлы,
  • генерировать код на основе внешних данных,
  • оптимизировать конструкции статически.

Пример: макрос, встраивающий содержимое файла как строку:

(defmacro embed-file (path)
(let ((content (uiop:read-file-string path)))
`(defconstant +embedded-content+ ,content)))

(Здесь используется uiop — портабельная библиотека для Common Lisp.)


Управляющие конструкции — условия, циклы и итерация​

1. Условные конструкции​

Common Lisp предоставляет несколько уровней условных выражений — от простых до сложных многоуровневых ветвлений.


1.1. if​

Базовая двоичная условная конструкция.

(if условие
форма-если-истина
форма-если-ложь)

Ветка "ложь" необязательна. Если она отсутствует и условие ложно, результат — NIL.


1.2. when и unless​

Упрощённые формы для одностороннего ветвления.

(when условие
форма1
форма2...)

(unless условие
форма1
форма2...)

Эквивалентны:

(when p body...) → (if p (progn body...) nil)
(unless p body...) → (if p nil (progn body...))

1.3. cond​

Многоусловное ветвление. Каждая ветка — список, начинающийся с условия.

(cond
(условие1 тело1...)
(условие2 тело2...)
...
(t тело-по-умолчанию...))

Выполняется первая ветка с истинным условием. Если ни одно условие не истинно и нет ветки (t ...), результат — NIL.


1.4. case, ecase, ccase​

Выбор по значению ключа. Работает с eql-сравнением.

(case ключ
(значение1 тело1...)
((значение2 значение3) тело2...)
(otherwise тело-по-умолчанию))

Пример:

(case op
(+ (add a b))
(- (sub a b))
((*) (mul a b))
(otherwise (error "Unknown operator")))
  • ecase — как case, но вызывает ошибку, если ключ не найден.
  • ccase — интерактивная версия, позволяет исправить ключ во время ошибки.

1.5. typecase, etypecase, ctypecase​

Ветвление по типу объекта.

(typecase x
(integer (* x 2))
(string (length x))
(list (mapcar #'1+ x)))

Аналогично:

  • etypecase — ошибка при несовпадении всех типов.
  • ctypecase — интерактивное исправление.

2. Циклы​

Common Lisp предлагает как низкоуровневые, так и высокоуровневые средства итерации.


2.1. Макрос loop​

Самый мощный и гибкий инструмент итерации. Имеет два синтаксиса:

  • Простой: (loop for var from 1 to 10 do (print var))
  • Расширенный — с аккумуляторами, условиями, параллельными итерациями.

Основные конструкции loop​
  • Итерация по числам:
(loop for i from 1 to 5 do (print i))
(loop for i from 10 downto 1 by 2 collect i) ; → (10 8 6 4 2)
  • Итерация по списку:
(loop for x in '(a b c) do (print x))
(loop for x in '(1 2 3) collect (* x x)) ; → (1 4 9)
  • Итерация по вектору или строке:
(loop for x across #(1 2 3) sum x) ; → 6
(loop for c across "hello" collect c) ; → (#\h #\e #\l #\l #\o)
  • Параллельные итерации:
(loop for x in '(1 2 3)
for y in '(a b c)
collect (list x y)) ; → ((1 A) (2 B) (3 C))
  • Аккумуляторы:

    • collect — собирает значения в список.
    • append — объединяет списки.
    • sum — суммирует числа.
    • count — подсчитывает истинные значения.
    • maximize, minimize — находят экстремумы.
  • Условия:

(loop for x in '(1 2 3 4 5)
when (evenp x)
collect x) ; → (2 4)
  • Завершение:
(loop for i from 1
while (< i 10)
do (print i))
  • Возврат результата:
(loop for x in list
if (plusp x) sum x into pos-sum
else sum x into neg-sum
finally (return (list pos-sum neg-sum)))

Макрос loop стандартизирован в ANSI Common Lisp и поддерживается всеми реализациями.


2.2. do и do*​

Низкоуровневые циклы с явным управлением переменными.

Синтаксис:

(do ((var1 init1 step1)
(var2 init2 step2)
...)
(конечное-условие результат...)
тело...)
  • do — шаговые выражения вычисляются параллельно.
  • do* — шаговые выражения вычисляются последовательно.

Пример факториала:

(defun factorial (n)
(do ((i n (- i 1))
(acc 1 (* acc i)))
((<= i 1) acc)))

2.3. Специализированные итерационные макросы​
dolist​

Итерация по списку.

(dolist (var list [результат])
тело...)

Пример:

(dolist (x '(1 2 3))
(print (* x x)))

Если указан результат, он возвращается после завершения:

(dolist (x '(1 2 3) 'done)
(print x)) ; печатает 1, 2, 3 → возвращает DONE

dotimes​

Итерация по целым числам от 0 до N-1.

(dotimes (var count [результат])
тело...)

Пример:

(dotimes (i 5)
(print i)) ; 0, 1, 2, 3, 4

do-symbols, do-external-symbols, do-all-symbols​

Итерация по символам пакета.

(do-symbols (sym (find-package "CL"))
(when (fboundp sym)
(print sym)))

with-package-iterator​

Низкоуровневый итератор по символам пакета с фильтрацией.


3. Рекурсия и итерация​

Lisp поддерживает оба подхода. Выбор зависит от задачи:

  • Рекурсия — естественна для древовидных структур, обработки вложенных списков, символьных вычислений.
  • Итерация — предпочтительна для производительности, особенно при работе с большими линейными структурами.

Хвостовая рекурсия может быть оптимизирована в цикл компилятором (например, в SBCL), но это не гарантируется стандартом.

Пример: обход дерева

(defun tree-sum (tree)
(cond
((null tree) 0)
((numberp tree) tree)
(t (+ (tree-sum (car tree))
(tree-sum (cdr tree))))))

Тот же алгоритм через стек и итерацию будет быстрее на больших деревьях, но сложнее в написании.


4. Выход из циклов​

  • return — выходит из ближайшего block (все циклы создают неявный блок).
  • return-from имя — выходит из именованного блока.
  • loop-finish — специальная форма для немедленного завершения loop.

Пример:

(loop for x in '(1 2 3 4 5)
when (> x 3)
return x) ; → 4

5. Производительность итерации​

  • loop часто компилируется в эффективный код.
  • mapcar создаёт новый список — не подходит для больших данных без необходимости копирования.
  • dolist и dotimes — минимальные накладные расходы.
  • Для массивов предпочтительны loop ... across или aref вручную.

Обработка ошибок, условия и система рестартов​

Common Lisp обладает одной из самых продвинутых систем обработки исключительных ситуаций среди всех языков программирования — системой условий (Condition System). Она не ограничивается простым "выбросом и перехватом" ошибок, а предоставляет мощные средства для восстановления, перезапуска и интерактивного исправления состояния программы.


1. Условия (Conditions)​

Условие — это объект, представляющий исключительную или просто значимую ситуацию — ошибку, предупреждение, информационное сообщение и т.д.

Условия создаются с помощью функции error, warn, signal и других.


1.1. Типы условий​

Все условия наследуются от базового типа condition.

Основные подтипы:

  • serious-condition — серьёзные ситуации, требующие обработки.
    • error — фатальные ошибки.
      • simple-error
      • arithmetic-error, division-by-zero, overflow-error
      • control-error, parse-error, file-error, stream-error
    • warning — предупреждения.
      • simple-warning
    • storage-condition — нехватка памяти.
    • program-error — ошибки в коде (например, неверное количество аргументов).

Пользовательские условия определяются через define-condition.

Пример:

(define-condition invalid-age (error)
((age :initarg :age :reader invalid-age-value))
(:report (lambda (c stream)
(format stream "Invalid age: ~A" (invalid-age-value c)))))

2. Сигнализация условий​

2.1. error​

Сигнализирует ошибку и требует обработки. Если никто не обрабатывает — вызывается отладчик.

(error "Something went wrong")
(error 'division-by-zero :operation '/ :operands '(10 0))
(error 'invalid-age :age -5)

2.2. warn​

Сигнализирует предупреждение. Программа продолжает выполнение.

(warn "This is deprecated")

2.3. signal​

Сигнализирует условие, но не требует обработки. Используется для информационных или логгинг-событий.

(signal 'my-custom-condition :data some-value)

3. Обработка условий​

3.1. handler-case​

Перехватывает условия по типу, аналогично try/catch.

Синтаксис:

(handler-case выражение
(тип-условия (переменные...) тело...)
...)

Пример:

(handler-case (/ 10 0)
(division-by-zero () "Cannot divide by zero"))
; → "Cannot divide by zero"

Можно получать доступ к полям условия:

(handler-case (open "missing.txt")
(file-error (c)
(format nil "File error: ~A" (file-error-pathname c))))

3.2. ignore-errors​

Специальный макрос, который перехватывает любые ошибки и возвращает два значения:

  • результат (или NIL при ошибке),
  • условие (или NIL, если ошибки не было).

Пример:

(multiple-value-bind (result error)
(ignore-errors (/ 10 0))
(if error
"Failed"
result))
; → "Failed"

3.3. handler-bind​

Более гибкий механизм, чем handler-case. Устанавливает динамические обработчики, которые могут:

  • просто записать лог,
  • вызвать рестарт,
  • продолжить выполнение.

Обработчики работают в динамическом окружении, а не как стек исключений.

Синтаксис:

(handler-bind ((условие обработчик) ...)
тело...)

Пример:

(handler-bind ((division-by-zero
(lambda (c)
(declare (ignore c))
(invoke-restart 'use-value 1))))
(/ 10 0))

Этот пример работает только если где-то определён рестарт use-value.


4. Рестарты (Restarts)​

Рестарт — это точка восстановления, предлагающая способ продолжить выполнение после ошибки.

Рестарты не прерывают поток управления — они просто становятся доступны при сигнализации условия.


4.1. restart-case​

Определяет возможные рестарты в текущем контексте.

Синтаксис:

(restart-case выражение
(имя-рестарта (аргументы...) тело...)
...)

Пример:

(defun safe-divide (a b)
(restart-case
(if (zerop b)
(error "Division by zero")
(/ a b))
(use-value (value)
:report "Use specified value instead"
value)
(retry-with (new-b)
:report "Retry with new divisor"
(safe-divide a new-b))))

Теперь при вызове (safe-divide 10 0) можно выбрать:

  • вернуть произвольное значение,
  • повторить деление с новым делителем.

4.2. with-simple-restart​

Упрощённая форма для одного рестарта.

(with-simple-restart (continue "Skip this step")
(some-risky-operation))

4.3. Вызов рестартов​
  • invoke-restart — вызывает рестарт по имени.
  • find-restart — ищет рестарт в текущем динамическом окружении.
  • compute-restarts — возвращает список всех доступных рестартов.

Пример автоматического выбора:

(handler-bind ((error
(lambda (c)
(declare (ignore c))
(let ((r (find-restart 'use-value)))
(when r
(invoke-restart r 42))))))
(safe-divide 10 0))
; → 42

5. Интерактивность и отладчик​

Если условие не обработано, Common Lisp запускает отладчик, который:

  • показывает стек вызовов,
  • выводит доступные рестарты,
  • позволяет интерактивно выбрать действие.

Это особенно полезно при разработке — можно исправить данные "на лету" и продолжить.


6. Практические шаблоны​

6.1. Защита от ошибок с возвратом значения по умолчанию​
(defun safe-read-file (path &optional (default ""))
(handler-case
(uiop:read-file-string path)
(file-error () default)))

6.2. Логирование предупреждений​
(handler-bind ((warning
(lambda (w)
(log-message "Warning: ~A" w)
(muffle-warning w)))) ; подавляет дальнейшее распространение
(some-operation))

6.3. Автоматический повтор при временной ошибке​
(defun retry-on-error (fn max-retries)
(loop for attempt from 1 to max-retries
do (restart-case
(return (funcall fn))
(retry ()
:report "Retry the operation"
(if (= attempt max-retries)
(error "Max retries exceeded")
;; просто продолжаем цикл
)))))

Объектная система CLOS (Common Lisp Object System)​

CLOS — это мощная, динамическая и стандартизированная объектно-ориентированная система в Common Lisp. Она поддерживает множественное наследование, обобщённые функции, методы специализации по нескольким аргументам, метаклассы и динамическую модификацию классов во время выполнения.


1. Классы​

Классы определяются с помощью defclass.

Синтаксис:

(defclass имя (суперклассы...)
((слот1 :initarg :arg1 :accessor acc1 :initform значение :type тип ...)
...)
(:метакласс имя)
(:documentation "Описание"))

1.1. Слоты (слоты = поля)​

Каждый слот может иметь следующие опции:

  • :initarg — ключевое слово для передачи значения при создании экземпляра.
  • :accessor — автоматически создаёт геттер и сеттер.
  • :reader — только геттер.
  • :writer — только сеттер.
  • :initform — значение по умолчанию.
  • :type — ограничение типа (не проверяется строго во всех реализациях).
  • :allocation — :instance (по умолчанию) или :class (статический слот, общий для всех экземпляров).

Пример:

(defclass person ()
((name :initarg :name :accessor name :initform "Anonymous")
(age :initarg :age :accessor age :initform 0)
(id :reader id :initform (gensym))))

2. Создание экземпляров​

Экземпляры создаются функцией make-instance.

(defvar p (make-instance 'person :name "Alice" :age 30))
(name p) ; → "Alice"
(age p) ; → 30

Если не указан :initarg, слот получает значение из :initform.


3. Обобщённые функции и методы​

В CLOS методы не принадлежат классам. Вместо этого определяются обобщённые функции, которые собирают методы по сигнатурам аргументов.


3.1. Определение обобщённой функции​

Можно определить без тела — только для документации:

(defgeneric describe-object (obj)
(:documentation "Returns a description of the object."))

3.2. Определение метода​

Методы определяются через defmethod.

(defmethod describe-object ((p person))
(format nil "Person ~A, age ~A" (name p) (age p)))

Здесь (p person) — специализированный параметр: метод вызывается, если первый аргумент является экземпляром person.


3.3. Множественная диспетчеризация​

Метод может специализироваться по нескольким аргументам.

(defclass document () ())
(defclass printer () ())

(defmethod print-document ((doc document) (prn printer))
(format t "Printing document on printer~%"))

При вызове (print-document doc prn) выбирается метод на основе типов обоих аргументов.


4. Наследование​

Класс может наследовать от нескольких суперклассов.

(defclass employee (person)
((salary :initarg :salary :accessor salary)))

(defclass student (person)
((university :initarg :university :accessor university)))

(defclass intern (employee student)
((mentor :initarg :mentor :accessor mentor)))

Порядок линеаризации (C3 linearization) определяет порядок поиска слотов и методов.


5. Инициализация — initialize-instance и shared-initialize​

Методы инициализации позволяют выполнять действия при создании объекта.

  • initialize-instance — вызывается после выделения памяти.
  • shared-initialize — вызывается как при создании, так и при reinitialize-instance.

Пример:

(defmethod initialize-instance :after ((p person) &key)
(format t "Created person: ~A~%" (name p)))

:after означает, что метод выполняется после стандартной инициализации.

Другие квалификаторы:

  • :before — до основного метода,
  • :around — оборачивает всё (может решить, вызывать ли остальное).

6. Изменение классов во время выполнения​

CLOS позволяет динамически изменять структуру класса.

(defclass person () ((name :accessor name)))
(defvar p (make-instance 'person))
(setf (name p) "Bob")

;; Добавляем слот age
(defclass person () ((name :accessor name) (age :accessor age :initform 0)))

В большинстве реализаций существующие экземпляры автоматически обновляются — новые слоты получают :initform.

Функция change-class позволяет изменить класс конкретного экземпляра:

(change-class p 'employee)

7. Интроспекция классов​

CLOS предоставляет богатые средства анализа:

  • (class-of obj) — класс объекта.
  • (find-class 'имя) — объект класса по имени.
  • (slot-exists-p obj 'слот) — существует ли слот.
  • (slot-boundp obj 'слот) — привязано ли значение к слоту.
  • (describe obj) — подробная информация.

Пример:

(slot-exists-p p 'name) ; → T
(slot-boundp p 'age) ; → T (если был задан или есть :initform)

8. Метаклассы​

Классы сами являются объектами, и их поведение определяется метаклассами.

Стандартный метакласс — standard-class.

Можно определить собственный метакласс, наследуя от standard-class, чтобы изменить:

  • способ хранения слотов,
  • поведение при создании,
  • правила наследования.

Пример (редко используется):

(defclass singleton-class (standard-class)
())

(defclass my-singleton ()
()
(:metaclass singleton-class))

Затем можно переопределить allocate-instance, чтобы обеспечить единственность экземпляра.


9. Практические рекомендации​

  • Используйте :accessor, а не отдельные :reader/:writer, если нужен и геттер, и сеттер.
  • Избегайте глубоких иерархий — CLOS лучше работает с плоскими, композиционными структурами.
  • Предпочитайте обобщённые функции прямому доступу к слотам — это даёт гибкость.
  • Используйте :after/:before методы для логирования, валидации, кэширования.

10. Пример — полиморфный рендеринг​

Код ITЗагрузка примера кода…


Компиляция, загрузка, модули и системы сборки​

Common Lisp предоставляет мощные средства для организации кода в проекты, управления зависимостями, компиляции и распространения программ. Основной инструмент современной экосистемы — ASDF (Another System Definition Facility).


1. Файлы и загрузка​

1.1. Загрузка исходного кода​

Функция (load "файл") читает и выполняет файл с расширением .lisp.

  • Если указано только имя без расширения, интерпретатор ищет .lisp, .lsp, .cl и другие варианты.
  • load может загружать как исходный, так и скомпилированный (.fasl, .fasb, .dx64fsl и т.д.) код.

Пример:

(load "my-program.lisp")

1.2. Компиляция​

Компиляция ускоряет выполнение и позволяет создавать автономные образы.

Основные функции:

  • (compile-file "input.lisp") — компилирует файл в машинный код, создаёт .fasl.
  • (load "input.fasl") — загружает скомпилированный файл (обычно быстрее).
  • (compile 'имя-функции) — компилирует отдельную функцию.

Пример:

(compile-file "utils.lisp") ; → "utils.fasl"
(load "utils.fasl")

Большинство реализаций (SBCL, CCL) автоматически компилируют функции при определении в REPL, но явная компиляция нужна для финальной сборки.


2. Пакеты (Packages)​

Пакет — это пространство имён для символов. Он решает проблему конфликтов имён между библиотеками.


2.1. Создание пакета​
(defpackage :my-app
(:use :cl) ; импортировать все символы из CL
(:export :main :process-data)
(:shadowing-import-from :other-lib :map))
  • :use — наследует символы из другого пакета.
  • :export — делает символы доступными другим.
  • :import-from — импортирует конкретные символы.
  • :shadowing-import-from — импортирует с перекрытием локальных.

2.2. Переключение контекста​
(in-package :my-app)

Все последующие определения принадлежат пакету my-app.


2.3. Доступ к символам других пакетов​
  • other::internal-symbol — внутренний символ (не экспортирован).
  • other:public-symbol — экспортированный символ.

3. ASDF — система определения проектов​

ASDF — стандарт де-факто для управления проектами в Common Lisp.


3.1. Структура проекта​

Типичная структура:

my-project/
├── my-project.asd
├── src/
│ ├── main.lisp
│ └── utils.lisp
└── README.md

3.2. Файл .asd​

Определяет систему:

(defsystem "my-project"
:version "0.1.0"
:author "Author <author@example.com>"
:license "MIT"
:description "A sample project"
:depends-on ("alexandria" "uiop")
:components ((:module "src"
:components
((:file "utils")
(:file "main" :depends-on ("utils")))))
:build-operation "program-op"
:entry-point "my-project:main")
  • :depends-on — список зависимостей (библиотек из Quicklisp или локальных систем).
  • :components — файлы и модули.
  • :entry-point — функция для запуска (при создании исполняемого файла).

3.3. Загрузка системы​

В REPL:

(asdf:load-system "my-project")

Или через Quicklisp:

(ql:quickload "my-project")

4. Quicklisp — менеджер пакетов​

Quicklisp — централизованный каталог библиотек.

Установка:

(quickload "drakma") ; HTTP-клиент
(quickload "cl-ppcre") ; регулярные выражения

Quicklisp автоматически скачивает зависимости и добавляет их в asdf:*central-registry*.


5. Создание исполняемых файлов​

Некоторые реализации позволяют создавать standalone-программы.


5.1. SBCL​
(sb-ext:save-lisp-and-die "my-app"
:executable t
:toplevel #'my-project:main)

Результат — исполняемый двоичный файл, не требующий Lisp-окружения.


5.2. CCL​
(ccl:save-application "my-app" :toplevel-function #'my-project:main)

6. Управление путями и портабельность​

Библиотека UIOP (Universal Implementation-dependent Operations Platform) входит в большинство реализаций и обеспечивает:

  • Портабельную работу с файлами: (uiop:pathname-directory-pathname *load-pathname*)
  • Чтение файлов: (uiop:read-file-string "data.txt")
  • Запуск внешних процессов: (uiop:run-program '("ls" "-l"))
  • Обработка аргументов командной строки: (uiop:command-line-arguments)

Пример портабельной загрузки ресурсов:

(defun resource-path (name)
(merge-pathnames name (uiop:pathname-directory-pathname *load-pathname*)))

(uiop:read-file-string (resource-path "config.json"))

7. Сборка и развёртывание​

Процесс типичной сборки:

  1. Установить зависимости через Quicklisp.
  2. Загрузить систему: (asdf:load-system "my-project").
  3. Протестировать.
  4. Скомпилировать: (asdf:compile-system "my-project").
  5. Создать исполняемый файл (если нужно).

Для CI/CD можно использовать скрипты на Bash или Makefile:

build:
sbcl --noinform --eval "(ql:quickload :my-project)" \
--eval "(sb-ext:save-lisp-and-die \"my-app\" :executable t :toplevel #'my-project:main)" \
--quit

8. Стандартные соглашения​

  • Имя системы совпадает с именем основного пакета.
  • Все файлы кода — в подкаталоге src/.
  • Тесты — в t/ или test/, как отдельная система "my-project/test".
  • Документация — в docs/ или в виде строк документации внутри кода.

9. Отладка загрузки​

Если система не загружается:

  • Проверьте, что .asd файл находится в пути, известном ASDF (asdf:*central-registry*).
  • Используйте (asdf:system-relative-pathname "my-project" "src/main.lisp") для относительных путей.
  • Включите логгинг: (setf asdf:*verbose-out* *standard-output*).

Системные переменные, параметры окружения, отладка и интроспекция​

Common Lisp предоставляет богатый набор динамических переменных, системных параметров и инструментов интроспекции, позволяющих управлять поведением программы, анализировать состояние выполнения и настраивать взаимодействие с окружением.


1. Специальные (динамические) переменные​

В Common Lisp есть два типа переменных:

  • Лексические — привязки через let, видны только в своём блоке.
  • Специальные (динамические) — имеют динамическую область видимости, передаются "вниз" по стеку вызовов.

Глобальные переменные, определённые через defvar или defparameter, являются специальными.


1.1. defvar и defparameter​
  • (defvar *имя* значение) — инициализирует переменную только если она не определена. Используется для конфигурационных параметров.
  • (defparameter *имя* значение) — всегда устанавливает значение. Используется для значений по умолчанию.

По соглашению, имена специальных переменных окружаются звёздочками: *standard-output*, *debug-io*.

Пример:

(defparameter *max-retries* 3)
(defvar *log-level* :info)

1.2. Динамическое переназначение​

Специальные переменные можно временно переопределять через let:

(let ((*standard-output* (make-string-output-stream)))
(print "Hello")
(get-output-stream-string *standard-output*)) ; → "Hello"

Это позволяет перенаправлять вывод, логирование, поведение функций без изменения их кода.


2. Основные системные переменные​

ПеременнаяНазначение
*standard-input*Поток ввода по умолчанию
*standard-output*Поток вывода по умолчанию
*error-output*Поток для ошибок
*debug-io*Двуликий поток (ввод/вывод) для отладчика
*query-io*Поток для интерактивных запросов
*package*Текущий пакет
*readtable*Таблица чтения (управляет синтаксисом)
*features*Список фич реализации (для условной компиляции)
*modules*Список загруженных модулей
*load-pathname*Путь к файлу, который сейчас загружается
*load-truename*Канонический путь к загружаемому файлу

Пример использования *features*:

#+sbcl (format t "Running on SBCL~%")
#+ccl (format t "Running on Clozure CL~%")

Конструкции #+ и #- позволяют включать/исключать код на этапе чтения в зависимости от фич.


3. Отладка​

3.1. trace и untrace​

Включает трассировку вызовов функции:

(trace factorial)
(factorial 5)
;; Вывод:
;; 1. Trace: (FACTORIAL '5)
;; 2. Trace: (FACTORIAL '4)
;; ...
;; 2. Trace: FACTORIAL ==> 24
;; 1. Trace: FACTORIAL ==> 120

Отключение:

(untrace factorial)

Можно трассировать несколько функций: (trace f1 f2 f3).


3.2. break​

Останавливает выполнение и запускает отладчик:

(defun risky-fn (x)
(when (< x 0) (break "Negative input!"))
(* x x))

3.3. inspect​

Открывает интерактивный инспектор для объекта:

(inspect my-object)

Позволяет просматривать слоты, внутреннюю структуру, ссылки.


3.4. describe​

Выводит информацию об объекте в поток:

(describe *standard-output*)
; → #<SYNONYM-STREAM to *TERMINAL-IO*>

Работает с функциями, классами, пакетами, потоками.


4. Профилирование и измерение времени​

4.1. time​

Измеряет время выполнения, количество выделенной памяти, сборок мусора:

(time (some-heavy-computation))
; → Evaluation took:
; 0.002 seconds of real time
; 1,000,000 bytes allocated

4.2. Профайлеры реализаций​
  • SBCL: (sb-profile:profile функция), затем (sb-profile:report).
  • CCL: встроенный профайлер через (ccl:profile функция).

Пример (SBCL):

(sb-profile:profile "MY-PACKAGE")
(run-benchmark)
(sb-profile:report)
(sb-profile:unprofile)

5. Интроспекция среды выполнения​

5.1. Информация о реализации​
(lisp-implementation-type) ; → "SBCL"
(lisp-implementation-version) ; → "2.4.0"
(machine-type) ; → "X86-64"
(machine-instance) ; → "my-pc"
(short-site-name) ; → "LOCAL"

5.2. Управление памятью​
  • (room) — выводит статистику по использованию памяти.
  • (gc) — принудительный запуск сборщика мусора.
  • (room t) — подробная разбивка по областям памяти.

5.3. Анализ вызовов​

Функция (backtrace) или (print-backtrace) (в зависимости от реализации) выводит стек вызовов.

В SBCL:

(sb-debug:backtrace)

6. Настройка поведения системы​

6.1. Уровень отладки​

Некоторые реализации используют специальные переменные:

  • SBCL: *efficiency-note-cost-threshold*, *efficiency-note-limit*
  • CCL: ccl:*fasl-save-definitions*

6.2. Компиляторные параметры​

Через декларации можно влиять на оптимизацию:

(declaim (optimize (speed 3) (safety 1) (debug 0)))

Уровни от 0 до 3:

  • speed — скорость выполнения,
  • safety — проверки во время выполнения,
  • debug — информация для отладчика,
  • space — экономия памяти.

Пример безопасного режима:

(declaim (optimize (safety 3) (debug 3)))

7. Работа с окружением операционной системы​

7.1. Переменные окружения​

Через UIOP:

(uiop:getenv "HOME") ; → "/home/user"
(uiop:setenv "MY_VAR" "42") ; устанавливает переменную

7.2. Аргументы командной строки​
(uiop:command-line-arguments) ; → ("--verbose" "input.txt")

7.3. Текущий рабочий каталог​
(uiop:getcwd) ; → #P"/home/user/project/"
(uiop:chdir #P"/tmp/") ; изменить каталог

8. Практические шаблоны​

8.1. Временное перенаправление вывода​
(with-output-to-string (s)
(let ((*standard-output* s))
(run-logging-function))
s) ; возвращает строку с логом

8.2. Условная компиляция под реализацию​
#+sbcl
(defun get-threads ()
(sb-thread:all-threads))

#+ccl
(defun get-threads ()
(ccl:all-processes))

8.3. Безопасное выполнение с логированием ошибок​
(handler-bind ((error (lambda (c)
(format *error-output* "Error: ~A~%" c)
(muffle-warning c))))
(risky-operation))

Практические идиомы, стиль кода и шаблоны​

Рекомендации по чистому и поддерживаемому Common Lisp — именование, макросы, производительность, типичные паттерны. Для обучения с нуля начните с введения в раздел.


1. Соглашения об именовании​

Common Lisp использует kebab-case (слова через дефис) для имён функций, переменных и классов.

  • Функции и переменные — process-data, max-retries, *debug-mode*
  • Предикаты (возвращают логическое значение): заканчиваются на p или -p
    → listp, evenp, file-exists-p
  • Деструктивные функции (изменяют аргументы): заканчиваются на r
    → nconc, sort (деструктивен), nreverse
  • Макросы: без специального суффикса, но часто выражают управляющие конструкции
    → with-open-file, when, unless

Специальные (динамические) переменные окружены звёздочками:
→ *standard-output*, *features*, *my-config*

Внутренние (приватные) символы не экспортируются из пакета. Их можно помечать одним подчёркиванием в начале, хотя это не обязательно: _helper-fn.


2. Организация кода​

2.1. Модульность​
  • Каждый файл — одна логическая единица — класс, набор функций, DSL.
  • Избегайте "мусорных" файлов с разнородным кодом.
  • Используйте defpackage в отдельном файле или в начале основного файла модуля.

2.2. Зависимости​
  • Минимизируйте зависимости между модулями.
  • Используйте :use только для :cl и, возможно, одной вспомогательной библиотеки.
  • Предпочитайте :import-from явному указанию префиксов (alexandria:flatten).

2.3. Документация​
  • Все публичные функции — с (documentation 'fn 'function).
  • Используйте строки документации в defclass, defgeneric, defsystem.
  • Пример:
(defun safe-divide (a b)
"Divides A by B. Returns NIL if B is zero."
(if (zerop b) nil (/ a b)))

3. Безопасные макросы​

Макросы — мощный инструмент, но требуют дисциплины.


3.1. Гигиена​
  • Всегда используйте gensym для внутренних переменных.
  • Не захватывайте пользовательские символы.

Плохо:

(defmacro bad-let ((var val) &body body)
`(let ((temp ,val))
(let ((,var temp)) ,@body))) ; конфликт, если пользователь использует TEMP

Хорошо:

(defmacro good-let ((var val) &body body)
(let ((g (gensym "VAL")))
`(let ((,g ,val))
(let ((,var ,g)) ,@body))))

3.2. Чёткая семантика​
  • Макрос должен вести себя как встроенная форма языка.
  • Избегайте побочных эффектов в аргументах (вычисляйте их один раз).
  • Предоставляйте :report для рестартов.

3.3. Тестирование макросов​
  • Тестируйте раскрытие через macroexpand-1.
  • Проверяйте поведение в контексте: локальные переменные, возврат значений.

4. Производительность​

4.1. Выбор структур данных​
  • Для частого доступа по индексу — векторы (aref O(1)).
  • Для последовательного обхода — списки.
  • Для поиска по ключу — хэш-таблицы (gethash O(1) в среднем).

4.2. Избегание аллокаций​
  • Переиспользуйте временные структуры через :fill-pointer и vector-push-extend.
  • Используйте with-output-to-string вместо конкатенации строк в цикле.

4.3. Компиляция​
  • Всегда компилируйте перед релизом: (asdf:compile-system "my-app").
  • Указывайте декларации типов для критичных участков:
(declaim (ftype (function (fixnum fixnum) fixnum) fast-add))
(defun fast-add (a b)
(declare (fixnum a b))
(+ a b))

4.4. Профилирование​
  • Не оптимизируйте без замеров.
  • Используйте time, sb-profile, ccl:profile.

5. Распространённые шаблоны​

5.1. with- макросы для управления ресурсами​

Шаблон обеспечивает автоматическую очистку:

(defmacro with-open-file-safe ((var path) &body body)
`(with-open-file (,var ,path :if-does-not-exist nil)
(when ,var
,@body)))

Аналогично — with-hash-table-iterator, with-slots, with-accessors.


5.2. Аккумуляторы​

Для сбора данных в цикле:

(let ((result '()))
(dolist (x data)
(when (validp x)
(push x result)))
(nreverse result))

Или через loop:

(loop for x in data
when (validp x)
collect x)

5.3. Обработка опций через ключевые аргументы​

Гибкий API:

(defun render (template &key (output *standard-output*) (escape t) (context nil))
...)

Позволяет вызывать как (render tmpl :escape nil) без указания остальных.


5.4. Использование CLOS для стратегий​

Вместо длинных cond — полиморфизм:

(defgeneric validate (validator value))

(defmethod validate ((v email-validator) value)
(cl-ppcre:scan "^[\\w.-]+@[\\w.-]+$" value))

(defmethod validate ((v phone-validator) value)
(cl-ppcre:scan "^\\+7\\d{10}$" value))

6. Антишаблоны​

6.1. Избыточное использование eval​
  • eval медленный, небезопасный, ломает компиляцию.
  • Почти всегда можно заменить на макрос или замыкание.

6.2. Глобальные состояния без необходимости​
  • Избегайте глобальных переменных для передачи данных между функциями.
  • Передавайте явно или используйте динамические привязки только для конфигурации.

6.3. Рекурсия без аккумулятора на больших данных​
  • Может вызвать переполнение стека.
  • Используйте итерацию или хвостовую рекурсию с аккумулятором.

6.4. Неправильное сравнение​
  • Для чисел — =, для общих объектов — eql, equal, equalp.
  • eq — только для символов и cons-ячеек.
  • Сравнение строк — string=, а не equal.

7. Инструменты для поддержки качества​

  • SLIME (Superior Lisp Interaction Mode for Emacs) — лучшая среда разработки.
  • sly — альтернатива SLIME для современных Emacs.
  • clippy или lisp-lint — статический анализ (ограничен).
  • FiveAM, Prove — фреймворки для тестирования.

Пример теста на FiveAM:

(def-suite math-tests)
(in-suite math-tests)

(test square-test
(is (= (square 5) 25))
(is (= (square -3) 9)))

Запуск:

(run! 'math-tests)

Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.


В подборках​

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Справочники — Справочник по языку Fortran, Справочник по Pascal, Справочник по COBOL, Справочник по visual-basic, Справочник по Lua, Справочник по ассемблеру.