Язык Lisp: что это, S-выражения, макросы и диалекты

Язык Lisp: что это, S-выражения, макросы и диалекты Полезное

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

  1. Установите SBCL: в Linux из пакетного менеджера дистрибутива (например, sudo apt install sbcl в Debian и Ubuntu), в macOS через Homebrew (brew install sbcl), для Windows есть установщик на сайте проекта.
  2. Проверьте установку: sbcl --version печатает номер версии.
  3. Выберите редактор с интерактивной связью с REPL: Emacs со SLIME или SLY — классический вариант; для VS Code есть расширения под Common Lisp.
  4. Библиотеки подключайте менеджером Quicklisp.
  5. Первый артефакт — файл вроде 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 — стандартизированный язык с нативными реализациями и изменяемыми структурами по умолчанию.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)