Lisp — это семейство языков программирования, в которых и программа, и данные записываются одинаково: в виде списков в круглых скобках (S-выражений). Сегодня под словом «Lisp» обычно имеют в виду не один язык, а группу диалектов: Common Lisp, Scheme, Clojure, Racket, Emacs Lisp и другие.
Содержание
Ниже — история, синтаксис, «код как данные», макросы и различия диалектов. Примеры — на Common Lisp, проверены в SBCL.
Мини-словарь: четыре слова, которые путают
| Термин | Что это | Пример |
|---|---|---|
| Атом | Неделимое значение: число, строка, символ | 42, "текст", average |
| Список | Последовательность элементов в скобках, внутри может быть что угодно, включая другие списки | (4 5 3 5) |
| S-выражение | Общее название для атома или списка — так записан любой фрагмент программы | (+ 1 2) |
| Форма | S-выражение, которое предназначено для вычисления | (average '(4 5)) |
Важная граница: не каждое S-выражение — корректная форма. Список (1 2 3) — нормальные данные, но как форма он ошибочен, потому что 1 не функция. Ниже это видно на реальной ошибке.
Коротко об истории
Lisp предложил Джон Маккарти в 1958 году в MIT, в рамках работ по искусственному интеллекту. Основная статья о языке вышла в 1960 году. Идею обработки списков Маккарти взял из более раннего языка IPL (Ньюэлл, Шоу, Саймон), но сделал ее основой всего языка.
Первый интерпретатор написал Стив Рассел для IBM 704: он заметил, что функцию eval из статьи Маккарти можно реализовать напрямую. В Lisp впервые появились многие привычные сегодня вещи: автоматическая сборка мусора, условное выражение if как выражение, функции как значения, интерактивная среда REPL.
В 1970-80-х годах существовали Lisp-машины — компьютеры, аппаратно оптимизированные под Lisp (Symbolics, LMI, Texas Instruments). Рынок их не пережил 1990-е. Разрозненные диалекты того времени свели в стандарт Common Lisp (ANSI, 1994), он действует до сих пор.
Как выглядит программа на Lisp
Вызов записывается в префиксной форме — сначала операция, потом аргументы, все в скобках: вместо 1 + 2 + 3 пишут (+ 1 2 3). Приоритетов операторов нет, порядок задают скобки.
Минимальный полный пример. Сохраните в файл main.lisp и запустите командой sbcl --script main.lisp:
;; Файл main.lisp: запуск - sbcl --script main.lisp
(defun average (numbers)
"Среднее арифметическое непустого списка чисел."
(/ (reduce #'+ numbers) (length numbers)))
(defparameter *scores* '(4 5 3 5))
(format t "Список: ~a~%" *scores*)
(format t "Первый элемент: ~a, остальные: ~a~%" (car *scores*) (cdr *scores*))
(format t "Среднее: ~a (~,2f)~%" (average *scores*) (average *scores*))
(format t "Удвоенные: ~a~%" (mapcar (lambda (x) (* 2 x)) *scores*))
;; Код как данные: собираем выражение списком и вычисляем его
(defparameter *expr* (list '+ 1 2 3))
(format t "Выражение: ~a -> ~a~%" *expr* (eval *expr*))
Результат прогона:
Список: (4 5 3 5)
Первый элемент: 4, остальные: (5 3 5)
Среднее: 17/4 (4.25)
Удвоенные: (8 10 6 10)
Выражение: (+ 1 2 3) -> 6
Разбор по строкам:
defunопределяет функциюaverage, строка под заголовком — ее документация. Функция рассчитана на непустой список: на пустом будет деление на ноль.'(4 5 3 5)— список-данные. Апостроф (сокращение отquote) говорит «не вычисляй, это просто список».carвозвращает первый элемент списка,cdr— остаток. Названия исторические, из регистров IBM 704; в Common Lisp у них есть синонимыfirstиrest.- Среднее вывелось как
17/4: деление целых в Common Lisp дает точную дробь (тип ratio), а не округленное число. Десятичный вид показывает директива формата~,2f. mapcarприменяет анонимную функциюlambdaк каждому элементу — это функция высшего порядка.- Последние две строки — суть Lisp: выражение
(+ 1 2 3)собрано обычной функциейlistкак данные, аevalвычислило его как код.
Типичная ошибка: забыли апостроф
Новичок пишет список без quote, рассчитывая получить данные:
(format t "~a~%" (car (1 2 3)))
Lisp пытается вычислить (1 2 3) как вызов функции с именем 1. Фактический результат в SBCL:
; in: FORMAT T
; (1 2 3)
;
; caught ERROR:
; illegal function call
Дальше SBCL печатает Unhandled SB-INT:COMPILED-PROGRAM-ERROR и завершает скрипт с ошибкой.
Исправление — явно пометить список как данные или собрать его функцией:
(format t "~a~%" (car '(1 2 3))) ; 1
(format t "~a~%" (car (list 1 2 3))) ; 1
Точный текст ошибки зависит от реализации: в SBCL он такой, в CLISP или CCL сообщение будет другим, но причина та же.
Код как данные и макросы
То, что программа на Lisp — это обычные списки, называют гомоиконностью («код как данные»). Практическое следствие — макросы: функции, которые выполняются до вычисления программы, получают код в виде списков и возвращают новый код.
Важно различать: функция получает уже вычисленные значения аргументов, а макрос получает сами выражения, еще не вычисленные. Поэтому макросом можно добавить в язык новую управляющую конструкцию. Пример — своя версия unless («выполнить, если условие ложно»):
(defmacro my-unless (condition &body body)
`(if ,condition nil (progn ,@body)))
(format t "~s~%" (macroexpand-1 '(my-unless (> x 10) (print "мало") x)))
(let ((x 7))
(my-unless (> x 10)
(format t "x = ~a, это не больше 10~%" x)))
Результат прогона:
(IF (> X 10)
NIL
(PROGN (PRINT "мало") X))
x = 7, это не больше 10
macroexpand-1 показывает, во что раскрылся макрос: в обычный if. Обратная кавычка задает шаблон кода, , подставляет значение, ,@ вставляет список выражений. Символы напечатались заглавными, потому что читатель Common Lisp по умолчанию приводит имена символов к верхнему регистру.
Граница: в учебном my-unless нет проблем с именами, но в реальных макросах, которые вводят свои переменные, нужна защита от захвата имен (gensym). В Common Lisp макросы не гигиеничны по умолчанию, в Scheme и Racket — гигиеничны.
Особенности Lisp без мифов
| Утверждение | Как на самом деле |
|---|---|
| «Lisp — чисто функциональный язык» | Нет. Common Lisp мультипарадигменный: есть присваивание, циклы (loop), объектная система CLOS. Функциональный стиль сильнее всего выражен в Clojure и Scheme |
| «В Lisp нет типизации» | Типизация динамическая, но строгая: (+ 1 "2") в SBCL падает с ошибкой «The value «2» is not of type NUMBER», без неявного приведения |
| «Lisp только интерпретируется» | Реализации вроде SBCL компилируют в машинный код, при этом сохраняя интерактивный REPL |
| «Много скобок — неудобно» | Скобки заменяют приоритеты операторов и отступы; редакторы подсвечивают пары и расставляют отступы автоматически |
Диалекты: что выбрать под задачу
| Диалект | Где работает | Для чего чаще берут |
|---|---|---|
| Common Lisp | Нативные реализации (SBCL, CCL, ECL и др.) | Стандартизированный язык общего назначения, долгоживущие системы, символьные вычисления |
| Scheme | Много реализаций | Минималистичное ядро, обучение, встраивание |
| Racket | Собственная платформа | Обучение программированию, создание своих языков (DSL) |
| Clojure | JVM, есть ClojureScript для JavaScript | Промышленная разработка на JVM, работа с данными и конкурентностью |
| Emacs Lisp | Внутри редактора Emacs | Настройка и расширение Emacs |
| AutoLISP | Внутри AutoCAD | Автоматизация чертежей |
Как выбирать: понять идеи Lisp и макросы — Common Lisp или Racket; промышленная разработка с библиотеками Java — Clojure (структуры данных в нем по умолчанию неизменяемые); автоматизация конкретной программы — Emacs Lisp или AutoLISP.
Как начать: среда для Common Lisp
- Установите SBCL: в Linux из пакетного менеджера дистрибутива (например,
sudo apt install sbclв Debian и Ubuntu), в macOS через Homebrew (brew install sbcl), для Windows есть установщик на сайте проекта. - Проверьте установку:
sbcl --versionпечатает номер версии. - Выберите редактор с интерактивной связью с REPL: Emacs со SLIME или SLY — классический вариант; для VS Code есть расширения под Common Lisp.
- Библиотеки подключайте менеджером Quicklisp.
- Первый артефакт — файл вроде
main.lispвыше, который запускается командойsbcl --script main.lisp.
Если не получилось:
- «illegal function call» — в форме на первом месте стоит не функция; чаще всего забыт апостроф перед списком-данными.
- Непарные скобки, файл не читается — включите в редакторе подсветку пар и автоотступы.
- Кириллица выводится знаками вопроса — проверьте, что локаль терминала использует UTF-8.
Выводы
- Lisp — семейство языков, а не один язык; действующий стандарт Common Lisp принят ANSI в 1994 году, из современных диалектов наиболее заметен Clojure.
- Синтаксис строится на S-выражениях и префиксной записи, программа и данные выглядят одинаково.
- «Код как данные» дает макросы: они преобразуют код до вычисления и позволяют добавлять в язык новые конструкции.
- Common Lisp не чисто функциональный и не бестиповый: он мультипарадигменный, с динамической строгой типизацией и компиляцией в машинный код.
- Выбор диалекта зависит от задачи: изучение идей — Common Lisp или Racket, промышленная разработка на JVM — Clojure.
Где применяется / связь с практикой
Освойте тему на практике
Идеи Lisp живут в промышленной разработке прежде всего через Clojure: он работает на JVM, использует библиотеки Java и сохраняет лисповые S-выражения, макросы и REPL-ориентированную разработку. Если хочется применять эти идеи в рабочих проектах, системно освоить язык поможет курс Clojure Developer.
Освойте тему на практике
Познакомиться с форматом обучения и смежными темами можно на бесплатных открытых уроках Otus.
FAQ
Используется ли Lisp в 2026 году?
Да, но в нишах: Clojure применяют в промышленной разработке на JVM, Common Lisp — в отдельных долгоживущих системах, Emacs Lisp и AutoLISP — для автоматизации своих программ. Массовым языком уровня Python или Java Lisp не является.
Стоит ли учить Lisp первым языком?
Можно, синтаксис у него минимальный, а Racket изначально создавался для обучения. Но для первой работы обычно выбирают более распространенный язык, а Lisp изучают как второй — ради идей макросов и функционального стиля.
Чем Clojure отличается от Common Lisp?
Clojure работает на JVM, по умолчанию использует неизменяемые структуры данных и кроме списков имеет литералы векторов [] и словарей {}. Common Lisp — стандартизированный язык с нативными реализациями и изменяемыми структурами по умолчанию.



