
Lisp: язык, которому 68 лет и который до сих пор подсказывает будущее
Осенью 1958 года математик Джон Маккарти приходит в Массачусетский технологический институт и начинает работу над языком, на котором можно было бы выразить рассуждения об «искусственном интеллекте» (термин, кстати, тоже его). Маккарти набрасывает математическую нотацию — формальное описание того, как вычислять символьные выражения с помощью функций. Эта нотация задумывалась как теоретический инструмент, не как настоящий язык программирования. Маккарти даже не собирался её реализовывать в коде. Для практических вычислений у него планировался другой, более «нормальный» синтаксис.
Тогда его аспирант, Стив Расселл, посмотрел на ту самую теоретическую функцию eval — фактически универсальный интерпретатор языка, выписанный на доске в виде математических уравнений, сказал, что её можно просто вручную перевести в машинный код IBM 704 и сделал это. К весне 1959-го у Расселла работал первый интерпретатор LISP, написанный целиком в кодах (тогда название ещё писалось капсом — как честная аббревиатура от LISt Processor). Маккарти потом будет вспоминать, что сам считал свою функцию eval чисто теоретической и был удивлён, когда Расселл превратил её в работающий код.
Этот момент — реализация теоретической математической нотации как настоящего языка — оказался одной из самых счастливых случайностей в истории нашей профессии. Lisp с этого дня живёт и развивается, ему исполняется 68 лет в 2026 году, и он по-прежнему остаётся одним из самых выразительных и идейно насыщенных языков программирования из существующих. На Lisp написан почти весь мир Emacs (через его диалект Emacs Lisp). На потомке Lisp — Clojure — написана значительная часть бэкендов многих современных интернет-компаний (Nubank, Walmart, Atlassian, Cisco). Уже семь поколений языков программирования учатся у Lisp, иногда сознательно копируя его конструкции, а иногда переоткрывая их под новыми именами.
Этот пост — об удивительной истории языка, идеи которого опередили эпоху на десятилетия и до сих пор не до конца догнаны. Я расскажу, откуда взялись скобки, S-выражения и car/cdr. Покажу, как Lisp подарил программированию сборку мусора, замыкания, функции первого класса, динамическую типизацию, рекурсию, метапрограммирование через макросы и REPL — то есть половину того, что мы сегодня считаем «современным». Доберусь до Лисп-машин и эпохи компании Symbolics, до раскола Maclisp/Interlisp и последующей стандартизации Common Lisp. Расскажу о Scheme и Clojure как двух самых живых его потомках. И покажу, почему десятый закон Гринспана — «всякая достаточно сложная программа на C или Fortran содержит спрятанную, медленную и плохо описанную реализацию половины Common Lisp» — до сих пор оказывается жестокой правдой о большинстве крупных проектов.
И, конечно, в финале — несколько слов о том, почему Lisp по-прежнему стоит изучать в 2026 году, даже если вы никогда не напишете на нём ни строчки продакшен-кода.
Маленькое замечание о написании
В этом тексте я последовательно пишу «Lisp» — с заглавной только первой буквой, как обычное имя собственное. Это современная конвенция, но она появилась не сразу.
Первые двадцать пять лет название писалось исключительно капсом — LISP. Это была аббревиатура от LISt Processor («обработчик списков»), и так её тогда и набирали — в статье Маккарти 1960 года, на обложках первых учебников, в академических работах 1960–70-х. Если открыть оригинальные исходники из MIT AI Lab, на каждой второй странице вы увидите «LISP» большими буквами.
Перелом случился в середине 1980-х, в момент стандартизации Common Lisp. Книга Гая Стила «Common Lisp the Language» (1984) уже последовательно использует «Lisp» как обычное имя, и всё сообщество перешло на такой вариант написания. Та же история произошла с другими старыми акронимами: FORTRAN превратился в Fortran, COBOL — в Cobol, BASIC — в Basic. Когда язык живёт достаточно долго и становится привычным культурным объектом, расшифровка аббревиатуры перестаёт быть актуальной, и капс уходит сам собой.
Это, кстати, неплохой исторический индикатор. Если вам сегодня попадается текст, где написано «LISP» большими буквами, — это с большой вероятностью либо историческая работа о Маккарти и эпохе MIT AI Lab, либо текст, написанный до середины 1980-х, либо стилизация под старину. Современные сайты Common Lisp, Scheme, Clojure, Racket — все пишут «Lisp». Так же делает Пол Грэм в эссе, Википедия в статьях, и стандарт ANSI X3.226-1994 в официальном тексте.
В этом посте я оставляю «LISP» только там, где специально говорю о первой реализации Маккарти и Расселла или цитирую старые работы. Всё остальное — «Lisp». В цитируемой ниже книге Герберта Стояна, например, в названии сохранён исторический капс — потому что это её настоящее, оригинальное название.
1. Маккарти, MIT и проект искусственного интеллекта
К 1958 году Джон Маккарти уже был известен. Он закончил аспирантуру в Принстоне у Соломона Лефшеца. Преподавал в Дартмуте, где летом 1956 года провёл легендарную Дартмутскую конференцию — двухмесячный семинар, на котором впервые прозвучал термин «искусственный интеллект» («artificial intelligence»), и который сейчас считается формальной точкой рождения области ИИ как самостоятельной дисциплины. Через два года Маккарти перешёл в MIT — там вместе с Марвином Минским создавал группу, которая в 1959-м официально стала MIT AI Lab.
Перед группой стояла задача: научить машину рассуждать. Что значит «рассуждать», точно никто не знал, но было понятно, что ничего похожего на привычные тогда языки программирования типа Fortran (1957) или COBOL (1959) для этой задачи не подойдёт. Fortran хорошо считал арифметические формулы, но не умел работать с тем, что Маккарти называл символьными выражениями — сложными иерархическими структурами вроде «функция от двух функций, первая из которых возвращает список троек».
Маккарти начал придумывать новый язык. Его конструкции были позаимствованы из лямбда-исчисления Алонзо Чёрча (1936), но с некоторыми существенными отличиями. От Чёрча — идея, что функция и её аргументы могут быть единообразно записаны как выражения, и что функция тоже может быть значением, передаваемым в другую функцию. От самого Маккарти — идея, что программу удобно записывать в виде той же самой структуры данных, с которой эта программа работает.
Эту идею потом назовут гомоиконичностью — «однородностью представления» (от греческого homo «одинаковый» + icon «образ»). На практике она означает, что программа на Lisp — это просто Lisp-список. Список вида (+ 1 2) — одновременно и данные (список из трёх элементов), и программа («применить операцию + к 1 и 2»). Эта простая на вид симметрия и есть та точка, из которой выросло почти всё, чем Lisp отличается от других языков.
2. S-выражения и их странные скобки
Маккарти изначально планировал два синтаксиса для своего языка. Один — для людей: близкий к привычной математической записи, с инфиксными операторами, более-менее естественный. Этот синтаксис он назвал M-expressions (Meta-expressions). Другой — для машины: упрощённый, единообразный, основанный на списках. Этот второй вариант — S-expressions (Symbolic expressions) — должен был быть всего лишь промежуточным представлением, в которое транслируется человеческий синтаксис перед исполнением.
Произошло, однако, обратное. Когда Стив Расселл реализовал интерпретатор, он принимал на вход именно S-выражения. Маккарти и его студенты, в ожидании пока кто-нибудь напишет транслятор из M в S, начали программировать прямо в S-выражениях. И обнаружили, что им так удобнее. Транслятор M-expressions, насколько известно, никогда не был доделан. S-выражения остались, M-выражения умерли.
Так Lisp получил свой знаменитый синтаксис из вложенных скобок. Программа вида:
(defun факториал (n)
(if (= n 0)
1
(* n (факториал (- n 1)))))
— это просто список из четырёх элементов: символа defun, символа факториал, списка параметров (n) и тела функции (которое само является списком). И это тело, в свою очередь, — список из четырёх элементов: символа if, проверочного выражения (= n 0), ветки «тогда» 1 и ветки «иначе» (* n (факториал (- n 1))).
Эта простота имеет драматическое последствие. Поскольку синтаксис языка тривиален — «список это последовательность элементов в скобках, элемент это либо атом, либо список», — никакого парсера в традиционном смысле не нужно. Любая программа на Lisp может с лёгкостью читаться другой программой на Lisp. И, если уж читаться, то и анализироваться, и видоизменяться. И, если уж видоизменяться, то новая версия может затем исполняться. Это и есть та петля, которая делает Lisp уникальным.
Когда программисты других языков впервые сталкиваются с этим водопадом скобок, у них обычно случается шок. Парижская акронимная шутка: Lisp расшифровывается как Lots of Irritating Silly Parentheses — «много раздражающих идиотских скобок». Шутка как шутка, но если вы посидите в Lisp хотя бы полчаса, скобки перестают раздражать: они начинают служить отступами и подсветкой структуры одновременно. Современные редакторы (Paredit и Parinfer в Emacs, vim-sexp в Vim, structural editing в большинстве IDE) делают навигацию по этому дереву совершенно естественной — вы оперируете не отдельными символами, а целыми выражениями.
3. CAR, CDR и наследство IBM 704
Список в Lisp реализован через связанные пары (cons-ячейки). Пара состоит из двух полей: первое поле хранит элемент, второе — указатель на остаток списка (тоже пару, или специальное значение «пустой список»). Это та же самая структура данных, которая теперь известна как связный список, но в Lisp она появилась практически на старте и стала основной.
Чтобы достать первый элемент пары, нужна была функция. Маккарти и его студенты назвали её car. Чтобы достать второе поле — функция cdr (произносится «коудер»). Эти странные имена не были придуманы для красоты — они остались от ассемблера IBM 704.
IBM 704 был построен на 36-битных машинных словах. Каждое слово делилось на несколько полей, и два самых частых поля назывались address part и decrement part. Соответствующие машинные инструкции для извлечения этих полей звались CAR (Contents of Address part of Register) и CDR (Contents of Decrement part of Register). Когда Расселл реализовывал cons-ячейку, он естественным образом разместил два её поля в этих двух частях машинного слова. И функции назывались по своим машинным инструкциям.
К 1962 году машины IBM 704 ушли в прошлое. К 1985-му даже их потомки исчезли из вычислительных центров. К 2026-му никто, кроме историков, не помнит, что значили эти аббревиатуры. А car и cdr остались — в Common Lisp, в Scheme, в Clojure (как first и rest, но традиционные имена тоже работают), в Emacs Lisp, в Racket. Это самая стойкая в истории программирования метка той аппаратной эпохи: машины давно нет, а названия функций используются каждый день.
Связанные пары в Lisp записываются точечной нотацией: (1 . 2) — это пара, где car = 1 и cdr = 2. Список (1 2 3) — на самом деле сокращение для (1 . (2 . (3 . nil))) — три вложенных пары, заканчивающиеся пустым списком. Список — частный случай пары, у которой в cdr тоже сидит список. Эта мелочь даёт огромную гибкость: пары можно соединять в деревья, графы, ассоциативные массивы, очереди — что угодно. Одна базовая структура, бесконечное количество применений.
4. Сборка мусора как побочный эффект
В 1959 году, во время реализации LISP, Маккарти столкнулся с проблемой, которой раньше никто не сталкивался: как освобождать память от cons-ячеек, которые больше не нужны.
В Fortran и языках того времени памяти было мало, и она распределялась статически: программист заранее объявлял все массивы и переменные, и операционная система выделяла под них фиксированные участки. В Lisp это не работало. Программа в любой момент могла создать новую cons-ячейку, например, при чтении S-выражения с входа. И в любой момент могла прекратить использовать какую-нибудь старую ячейку, оставив её болтаться без ссылок. Если эти неиспользуемые ячейки не освобождать, память кончится за несколько секунд работы интерпретатора. Если требовать от программиста явного освобождения (как потом будет в C через free), вся прелесть Lisp уходит: программист отвлекается на бухгалтерию памяти вместо того, чтобы думать о задаче.
Маккарти изобрёл то, что мы сегодня называем сборкой мусора (garbage collection). Идея была простая: время от времени интерпретатор останавливается, проходит по всем активным cons-ячейкам, помечает достижимые из «корней» (переменных и стека) как живые, а недостижимые объявляет мёртвыми и возвращает в пул свободной памяти. Так появился алгоритм mark-and-sweep, который до сих пор является основой большинства реализаций сборки мусора.
Сегодня сборка мусора есть почти везде — в Java, в Python, в Go, в Ruby, в JavaScript, в C# и в десятках других языков. Для нескольких поколений программистов это просто естественное свойство языка. Но придумал это в 1959 году Маккарти, как побочный эффект реализации Lisp. Если бы Lisp не появился, идея сборки мусора могла бы запоздать на десятилетия.
Я, кстати, про сборку мусора уже писал отдельный пост — там разобраны современные подходы (поколенческая, инкрементальная, конкурентная сборка) и почему она до сих пор остаётся областью активных исследований. Но база всего этого — Маккарти и его проблема с cons-ячейками.
5. 1960: знаменитая статья
В апреле 1960 года Маккарти опубликовал в Communications of the ACM статью с длинным названием «Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I». Это — формальная декларация Lisp как языка.
Статья короткая, всего тринадцать страниц, причём она актуальна до сих пор: почти всё, что в ней описано, всё ещё работает ровно так же. В ней Маккарти описывает:
- S-выражения и пары;
- семь базовых операторов (
atom,eq,car,cdr,cons,quote,cond); - лямбда-выражения как способ задавать функции;
- условное выражение
cond; - рекурсию как основной механизм вычисления;
- функцию
eval, которая принимает на вход Lisp-программу как S-выражение и интерпретирует её.
Эти семь операторов плюс eval — то самое, из чего собирается весь язык. Пол Грэм в своей классической статье «The Roots of Lisp» (2002) подробно реконструирует, как из этих кирпичей складывается всё, что нужно для серьёзного программирования. Грэм показывает, что Маккарти в 1960-м фактически собрал минимальный набор примитивов, эквивалентный машине Тьюринга, и собрал так удачно, что многие современные языки до сих пор основаны на тех же идеях, только в другом синтаксисе.
Вторая часть статьи Маккарти, кстати, никогда не вышла. Маккарти признавался, что обещал её в конце первой части на автомате, не имея конкретного плана. Никто не пожаловался.
6. 1962: первый компилятор
Интерпретатор Расселла был отличной демонстрацией, но работал медленно. Каждая операция требовала прохода по дереву S-выражения, разбора, диспетчеризации. Для серьёзных программ это не годилось.
В 1962 году Тим Харт и Майк Левин в MIT AI Lab написали первый компилятор Lisp — программу, которая принимала Lisp-функцию и выдавала машинный код. Компилятор был написан на самом Lisp, что само по себе было концептуальным прорывом. Это и есть тот трюк, который позже назовут раскруткой компилятора (англ. bootstrapping) — устоявшийся термин из учебников по теории трансляции (у Свердлова, в русском переводе Вирта): компилятор языка, написанный на самом этом языке, способен компилировать сам себя, и дальше развивается независимо от исходного представления.
Компиляция Lisp оказалась удивительно эффективной. Откомпилированная функция работала в 40 раз быстрее интерпретированной. С этого момента Lisp перестал быть «медленным языком» и стал инструментом, на котором можно писать серьёзные системы.
С появлением компилятора Lisp получил то, что мы сегодня считаем само собой разумеющимся: возможность запустить REPL (read-eval-print loop), где можно интерактивно вводить выражения, тут же исполнять их, видеть результат, тут же определять новые функции, и всё это в одной живой сессии. REPL — изобретение Lisp, появившееся ещё в 1960-х. Все современные интерактивные оболочки — IPython, Node.js, Julia, Erlang shell — наследники этого подхода.
7. Множественные Лиспы: Maclisp, Interlisp, и кембрийский взрыв
С 1962 по 1980 годы Lisp пережил период бурного размножения. Это было неизбежно: язык легко переписывался под новое железо (нужно было реализовать всего семь примитивов и eval), сообщество было небольшим и распределённым, а централизованной стандартизации в эпоху ARPANET ещё не существовало.
Появились (не претендую на полноту списка):
Maclisp (1966) — разработан в MIT в рамках Project MAC (отсюда название). На нём в 1968-м была написана знаменитая система компьютерной алгебры Macsyma — одна из первых программ для символьных математических вычислений, которая работает до сих пор как Maxima. Macsyma была настолько мощной для своего времени, что NASA её использовало для аналитических расчётов траекторий. На Maclisp работал ранний интерпретатор Emacs Эмерсона Голдсмита.
Interlisp (1966) — разработан в BBN, потом продолжен в Xerox PARC. Был знаменит своей интерактивной средой разработки: структурный редактор, отладчик, профайлер — всё то, что сегодня считается базовым набором IDE. Многие идеи Interlisp потом перекочевали в Smalltalk и оттуда — в современные среды разработки.
Franz Lisp (1978) — диалект для UNIX-машин, разработан в Беркли. Был популярен в начале 1980-х как доступный Lisp для академических кругов.
Scheme (1975) — об этом отдельный разговор ниже.
Standard Lisp (1969), MDL (1971), NIL (1979), T (1982), Le Lisp (1981), EuLisp (1990) — список можно продолжать.
К началу 1980-х было больше десяти серьёзных диалектов Lisp, и они были несовместимы между собой. Программу с Maclisp нельзя было запустить на Interlisp без переписывания.
8. Лисп-машины
Параллельно с программными диалектами происходила ещё одна удивительная вещь: для Lisp стали проектировать собственное железо.
Идея появилась в начале 1970-х в MIT AI Lab. Lisp-программы предъявляли к компьютеру специфические требования: динамическая типизация (значит, нужно проверять типы во время исполнения), сборка мусора (значит, нужно эффективно ходить по памяти), глубокая рекурсия (значит, нужен большой стек), интенсивная работа с указателями. Универсальные процессоры того времени делали всё это медленно. Возникла идея сделать процессор, изначально заточенный под Lisp.
В 1973 году Том Найт и Ричард Гринблат в MIT построили CONS Machine — первый прототип. В 1978-м появилась её улучшенная версия — CADR (название от Lisp-функции cadr, которая возвращает второй элемент списка, что является шуткой над тем, что это «вторая версия» машины). К началу 1980-х из MIT AI Lab выделились две коммерческие компании:
Lisp Machines Inc. (LMI) — основана в 1979 году группой во главе с Ричардом Гринблатом, с философией ближе к традиционному «хакерскому» подходу.
Symbolics — основана тогда же, в 1980 году, под руководством Расселла Нофтскера, с более бизнес-ориентированным подходом и желанием сделать Лисп-машину коммерческим продуктом для крупных корпоративных заказчиков.
Эти две компании раскололи MIT AI Lab пополам, и это стало одной из главных личных трагедий Ричарда Столлмана и одним из ключевых эпизодов, приведших его к идее свободного программного обеспечения. Расселл Нофтскер из Symbolics нанял большую часть сотрудников AI Lab, оставив лабораторию опустевшей. Столлман, оставшись практически один, в одиночку переписывал на стороне MIT всё, что выпускала Symbolics, чтобы у LMI был доступ к улучшениям. Это противостояние длилось два года и закончилось его уходом из MIT в 1984-м с проектом GNU.
Symbolics и LMI в течение 1980-х выпускали впечатляющие машины: Symbolics 3600, TI Explorer (от Texas Instruments), MacIvory (плата для Macintosh). На этих машинах работало то, что и сегодня кажется фантастикой: полноценная объектно-ориентированная среда разработки с динамической отладкой, средствами рефакторинга, инспектором объектов прямо в живой системе. Графический интерфейс, в котором каждое окно знало, какой объект оно отображает, и позволяло вызвать на этом объекте любую функцию через меню. По уровню инструментов разработки Лисп-машины 1985 года были на десять лет впереди всего остального, и в каком-то смысле до сих пор непобеждёнными.
Но рынок Лисп-машин рухнул. К 1990-му обычные рабочие станции (Sun SPARC, MIPS, Intel 386) стали достаточно мощными, чтобы Lisp бегал на них быстрее, чем на специализированном железе. Symbolics обанкротилась в 1996-м (формально дотянула до 2010-х в виде маленькой компании, поддерживающей старые системы). LMI закрылась ещё раньше. AI winter — кризис в исследованиях искусственного интеллекта конца 1980-х — добил остатки этого мира.
С Лисп-машинами умерла целая эпоха программистской культуры. Это, наверное, самая большая утрата в истории нашей профессии. Несколько поколений программистов жили в среде, в которой код был не файлом, который ты редактируешь и потом перекомпилируешь, а живой системой, в которой ты в любой момент мог переопределить любую функцию, увидеть результат, откатить, попробовать другую. И эта среда исчезла. Сейчас Emacs — последнее живое наследие той эпохи: примерно так же ощущается работа в Lisp-машине, только без графического богатства Symbolics.
9. 1984: Common Lisp и попытка примирения
К 1981-му стало ясно, что зоопарк диалектов Lisp стал серьёзной проблемой. Военное агентство DARPA, активно финансировавшее исследования по ИИ, было недовольно: их подрядчики писали программы на разных диалектах, и эти программы не могли взаимодействовать. Нужна была стандартизация.
С 1981 по 1984 годы группа главных архитекторов Lisp — Гай Стил (тот самый, что писал с Столлманом первый Emacs, а также соавтор Scheme и Java), Скотт Фалман (Carnegie Mellon, автор CMU Common Lisp и заодно изобретатель смайлика :-) в 1982-м), Дэниел Уайнреб (MIT, Symbolics), Дэвид Мун (Symbolics) и другие — заняли долгие переговоры о том, как объединить все основные идеи Maclisp, Lisp Machine Lisp, Spice Lisp, NIL, S-1 Lisp в единый стандарт.
Результат вышел в 1984 году в виде книги Гая Стила «Common Lisp the Language», которую все обычно зовут CLtL. Это была первая (и тогда — единственная) спецификация Common Lisp. В 1990-м вышло второе, существенно расширенное издание (CLtL2), которое описывало уже мощный, зрелый язык с CLOS — Common Lisp Object System.
В 1994-м Common Lisp получил официальный стандарт ANSI X3.226-1994 — один из крупнейших стандартов на язык программирования по объёму спецификации (около 1100 страниц). С тех пор стандарт не менялся ни разу. Это намеренно: Common Lisp в текущем виде считается «законченным» языком, и развитие идёт через библиотеки, а не через ядро.
CLOS — Common Lisp Object System — заслуживает отдельного упоминания. Это система объектного программирования, разработанная в первую очередь Грегором Кичалесом, Дэниелом Бобровом, Линдой ДеМайкл и Джимом Розом в середине 1980-х. CLOS впервые предложил множественное наследование с алгоритмически детерминированным разрешением (через так называемый C3-алгоритм), множественную диспетчеризацию (метод вызывается не по типу одного «себя», а по типам сразу нескольких аргументов) и систему мета-объектов (MOP), позволяющую программистам менять саму работу системы объектов из кода программы. Эти идеи в большинстве своём до сих пор не реализованы в основных мейнстримных языках. Java и C# до сих пор не имеют множественной диспетчеризации. Python научился чему-то похожему через functools.singledispatch только в 2014-м.
Грегор Кичалес из той же команды CLOS позже изобрёл аспектно-ориентированное программирование в Xerox PARC и стал автором языка AspectJ — что, опять же, корнями уходит в идеи MOP из Common Lisp.
10. Scheme: минимализм и его последствия
Параллельно Common Lisp, в 1975 году в MIT появился совсем другой диалект Lisp — Scheme. Его авторы — Гай Стил и Джеральд Сассмен — задумывали Scheme как минималистичную, академически чистую альтернативу. Там, где Common Lisp шёл по пути «возьмём всё хорошее и положим в коробку», Scheme шёл по пути «оставим только самое необходимое, всё остальное можно собрать поверх».
Scheme стал, наверное, самым важным диалектом Lisp с точки зрения влияния на теорию программирования. На Scheme построены примеры в знаменитой книге «Structure and Interpretation of Computer Programs» (SICP) — учебнике, по которому в MIT в 1980–90-х учили первокурсников программированию. SICP считается одной из лучших книг по программированию в истории. Она не учит конкретному языку — она учит, что такое вычисление как процесс, как программы становятся данными, и наоборот. Если вы прочли SICP, ваше отношение к программированию меняется навсегда.
Scheme принёс в Lisp-мир и в программирование вообще несколько важных идей:
Продолжения первого класса (англ. first-class continuations, оператор call/cc) — возможность взять «остаток вычисления» как объект и передать его дальше. Из call/cc выросло понимание управления потоком как обычных значений, что потом превратилось в современные async/await, генераторы, корутины.
Лексическая область видимости как стандарт. До Scheme многие диалекты Lisp использовали динамическую область видимости (переменная видна везде, где функция вызывается). Scheme сделал лексическую область (переменная видна только там, где она определена) основой языка. Все современные языки программирования теперь следуют именно этой модели.
Хвостовая рекурсия как обязательное требование к реализации. В Scheme рекурсивная функция, последним действием которой является вызов самой себя, не создаёт нового стекового кадра, а преобразуется в цикл. Это означало, что в Scheme можно было программировать чисто функционально, без явных циклов, не боясь переполнения стека.
Scheme получил два главных стандарта: серию RnRS (Revisedⁿ Report on the Algorithmic Language Scheme), от R5RS (1998) через R6RS (2007) к R7RS (2013). R5RS — самый минималистичный из них, помещается на пятидесяти страницах. R6RS — попытка добавить «промышленных» удобств, которая вызвала раскол в сообществе. R7RS — компромиссная версия.
Из академических потомков Scheme — Racket, который используется как язык для исследований в области создания языков программирования и для обучения. На Racket написан Pollen — язык для верстки книг, на нём же написана большая часть DrRacket — IDE, которая используется во множестве университетских курсов.
Из Scheme же произошёл Guile — встроенный язык расширения GNU-проектов. На Guile написаны конфигурации многих утилит GNU; на Guile написана операционная система GNU Guix (пакетный менеджер и дистрибутив Linux, в котором сами пакеты описаны как Scheme-программы).
11. Clojure: Lisp, который полюбила промышленность
В 2007 году Рич Хики — независимый разработчик, до этого 15 лет писавший на Common Lisp и C++ для коммерческих заказчиков — выложил в открытый доступ свой новый диалект Lisp под названием Clojure. Это была первая за десятилетия серьёзная попытка сделать Lisp, который смогли бы массово полюбить в индустрии.
Хики работал над Clojure в одиночку два с половиной года. Его собственная мотивация была простой: ему хотелось писать на Lisp, но он понимал, что без доступа к огромной экосистеме библиотек Common Lisp проигрывает Java и .NET. Хики решил встроить Lisp в JVM — взять синтаксис и идеи Lisp, но скомпилировать его так, чтобы он напрямую запускался на виртуальной машине Java и мог использовать любую Java-библиотеку без обёрток.
Эта идея оказалась удачной. Внезапно Clojure-программисты получили доступ ко всему экосистеме Java: HTTP-сервера, базы данных, очереди, веб-фреймворки — всё это стало доступно через простой вызов из Lisp-кода. В обратную сторону тоже работало: Java-разработчики могли встраивать Clojure-код в свои проекты постепенно, не переписывая всё.
К тому же Хики добавил несколько идей, которых не было в традиционном Lisp:
Иммутабельные структуры данных по умолчанию. В Clojure список, вектор, хеш-таблица не модифицируются. Если вы хотите добавить элемент в вектор, вы получаете новый вектор, а старый остаётся как был. Это сделано через persistent data structures — структуры, которые делают эффективное копирование с разделением (структуры с общими «хвостами» делят память). Идея заимствована из работ Криса Окасаки (его книга Purely Functional Data Structures 1998 года). Эта неизменяемость даёт огромный выигрыш в многопоточном программировании: если данные нельзя изменить, не нужно беспокоиться о гонках.
Software Transactional Memory (STM) — модель управления состоянием через транзакции, как в базах данных. Хочется поменять переменную? Заверни в транзакцию. Хочется поменять две связанные переменные согласованно? Тоже в транзакцию. Внутри одной транзакции изменения видны единым атомарным шагом. Это сильно проще для понимания, чем традиционные мьютексы и блокировки.
core.async — система каналов и горутин в стиле CSP (так же как в Go), реализованная как библиотека через макросы. То есть Clojure не нужно было встраивать корутины в ядро языка: их сделали поверх макросов, и они работают идентично нативным конструкциям.
ClojureScript — версия Clojure, компилируемая в JavaScript. Используется для написания фронтендов, часто в связке с библиотекой Reagent (обёртка над React).
Clojure быстро стал заметным в индустрии. Сегодня (2026 год) на нём пишут бэкенды Nubank (один из крупнейших цифровых банков Бразилии, на Clojure написана почти вся серверная часть), Walmart (e-commerce платформа), Apple, Atlassian, Cisco, CircleCI (на Clojure написан почти весь продукт), Funding Circle, Metabase (одна из главных open-source BI-платформ). Это, пожалуй, первый Lisp, на котором стало удобно писать обычный коммерческий код в обычной коммерческой компании без необходимости содержать команду из адептов.
Рич Хики в течение десяти лет читал доклады с базовыми идеями программирования, которые видны в Clojure: «Simple Made Easy» (2011, о разнице между «простым» и «знакомым»), «The Value of Values» (2012, об иммутабельных структурах данных), «Hammock-Driven Development» (2010, о роли времени и обдумывания в разработке). Эти доклады сегодня считаются обязательными к просмотру для любого инженера, который всерьёз задумывается о том, как устроена разработка софта и почему одни решения оказываются устойчивее других.
12. Десятый закон Гринспана и парадокс Lisp
В начале 1990-х американский программист Филип Гринспан сформулировал шуточный закон, который потом стал одним из самых цитируемых афоризмов о программировании:
Всякая достаточно сложная программа на C или Fortran содержит спрятанную, медленную, плохо документированную и плохо специфицированную реализацию половины Common Lisp.
Этот закон называется десятым законом Гринспана (хотя предыдущих девяти законов не существует — Гринспан пошутил и сразу сделал «десятый» как форма самопровозглашённой пословицы).
За шуткой стоит наблюдение, которое многократно подтверждалось. Возьмём любой крупный проект на C — Linux kernel, PostgreSQL, GCC, Git, nginx, любой большой коммерческий продукт. В каждом из них рано или поздно появляется:
- собственная система конфигурации с псевдо-языком (а это интерпретатор);
- собственный механизм событий и колбэков (а это асинхронное программирование первого класса);
- собственная система плагинов с динамической загрузкой кода (а это динамическая типизация и
eval); - собственный язык шаблонов или DSL для описания каких-то правил (а это макросистема);
- собственная сборка мусора, обычно реализованная через reference counting или arenas (а это сборка мусора).
То есть программа на «низкоуровневом» языке постепенно отращивает в себе всё то, что в Lisp было с первого дня, — но в неполном, плохо специфицированном, ad hoc виде. Закон Гринспана — это сатира на эту неизбежность.
Современным языкам программирования (Python, JavaScript, Go, Rust, Swift, Kotlin) тоже потребовались десятилетия, чтобы аккуратно вернуть себе те конструкции, которые в Lisp были с 1960-го: лямбды, замыкания, функции первого класса, сборка мусора, динамическая типизация, REPL. Каждое появление одной из этих фич в новом мейнстримном языке воспринимается как «прорыв» — а программисты на Lisp в это время смотрят и грустно улыбаются.
Маленькое пояснение про «первый класс» — я этот термин несколько раз употреблял по ходу текста, но толком не расшифровал. Сущность в языке программирования называется первоклассной (англ. first-class), если её можно использовать так же свободно, как обычные данные: класть в переменную, передавать в функцию как аргумент, возвращать из функции, складывать в массив или словарь. Например, целое число 42 в любом языке — первоклассная сущность: его можно куда угодно передать и где угодно сохранить.
А вот функции в большинстве старых языков первоклассными не были. В Fortran или раннем C функция — это не значение, которое можно куда-то положить, а именованный кусок кода, который можно только вызвать. В Lisp с 1958 года всё иначе: функция — обычное значение, его можно записать в переменную, отдать другой функции, вернуть как результат. Из этого выросли map, filter, reduce, колбэки, обработчики событий, замыкания, декораторы — половина того, чем мы пользуемся сегодня. Когда в Java только в 2014 году (Java 8) добавили лямбды, это было запоздалое подтягивание к тому, что в Lisp работало с самого начала.
То же самое слово «первоклассный» применимо и к более широким понятиям. Когда я выше сказал «SQL-запросы как первоклассные конструкции», это означает, что запрос в коде Clojure ведёт себя как настоящее выражение языка (его можно разобрать на части, переписать, скомбинировать с другими), а не как сырая строка, которую вы скармливаете драйверу базы данных.
Понимание того, что в языке можно сделать первоклассным практически что угодно — функции, типы, синтаксис, управление потоком, целые предметные области — и есть то, ради чего стоит однажды всерьёз погрузиться в Lisp. Об этом, собственно, и знаменитая фраза Эрика Реймонда из эссе «How to Become a Hacker»:
Lisp стоит изучать по другой причине: ради того глубокого просвещения, которое наступит, когда вы наконец его поймёте. Этот опыт сделает вас лучшим программистом до конца ваших дней, даже если на самом Lisp вы потом никогда не будете много писать.
Это одна из самых известных рекомендаций в нашей профессии. Я бы её повторил полностью: даже если вы никогда не напишете ничего серьёзного на Clojure или Common Lisp, проведите неделю с любым из них, разберите SICP до главы 4 (где авторы пишут на Scheme сам Scheme), пройдите Maccarthy’s eval своими руками. После этого многое в любом языке программирования начнёт казаться вам очевидным.
13. Гомоиконичность и макросы: главная сверхспособность
Я уже несколько раз упоминал, что программа на Lisp — это просто Lisp-список. А теперь объясню, что это даёт.
В большинстве языков программирования есть «синтаксис» и «семантика», и между ними стоит парсер. Когда вы пишете программу на Python, ваш текст — это символы и слова, которые читаются парсером, превращаются в абстрактное синтаксическое дерево (AST), и уже AST исполняется интерпретатором. Между текстом, который пишете вы, и деревом, которое исполняет машина, стоит непрозрачная стенка: чтобы как-то изменить или сгенерировать код, вам нужно работать с AST через специальные библиотеки (как ast в Python).
В Lisp этой стенки нет. То, что вы пишете, уже является деревом. Скобки явно показывают структуру. Никакого парсинга в привычном смысле не нужно — исходный текст и есть дерево, просто записанное в строчку. Это значит, что любая функция в Lisp может принимать на вход код, как обычные данные, манипулировать им и возвращать новый код. И вот этот возвращённый код можно тут же выполнить.
Это и есть макросы — функции, которые работают не во время исполнения, а во время компиляции: они принимают код, переписывают его в другой код, и уже этот другой код компилируется и исполняется. Макросы Lisp — это не текстовая подстановка, как макросы в C; это полноценное манипулирование деревьями. Внутри макроса вы используете весь язык, всю его мощь.
С помощью макросов можно встроить в Lisp практически любую языковую конструкцию, которую вы видели в других языках. Цикл for в стиле C? Несколько строк макроса. Декларативная маршрутизация HTTP-запросов как в Ruby on Rails? Несколько строк. Объявление компонента в стиле React? Несколько строк. SQL-запросы как первоклассные конструкции прямо в коде? Несколько строк (это реальная библиотека HoneySQL в Clojure).
Когда в Python в версии 3.10 (2021) появилось pattern matching, это было крупное событие — потребовался отдельный PEP, обсуждение длиной несколько лет, серьёзная работа над компилятором. В Lisp pattern matching — это просто библиотека, написанная любым желающим за пару дней с помощью макросов. Когда в JavaScript появился async/await, потребовалось встраивать новую конструкцию в стандарт ECMAScript. В Clojure async/await был сделан как библиотека core.async с помощью макросов.
Это — главная сверхспособность Lisp. Lisp не язык программирования. Это язык, на котором вы делаете языки программирования. Каждый серьёзный Lisp-проект через пару лет существования содержит свой собственный мини-язык, специально приспособленный для решения задачи проекта. Это называется language-oriented programming (языкоориентированное программирование, то есть подход, при котором под каждую конкретную задачу сначала проектируется удобный для неё мини-язык, а потом задача решается уже на нём), и в Lisp-мире это считается нормальным способом разработки.
Здесь, кстати, корень десятого закона Гринспана. Программы на других языках тоже отращивают свои мини-языки, но мучительно — через тонны boilerplate-кода (шаблонного, повторяющегося кода, который приходится писать руками снова и снова просто для того, чтобы обвязать друг с другом готовые куски). В Lisp это происходит естественно и почти без усилий.
14. Что в итоге выбрать в 2026 году
Если вы хотите попробовать Lisp в 2026 году, у вас есть несколько вариантов.
Clojure — самый практичный выбор. Если вы хотите писать на Lisp реальный продакшен-код, начинайте с него. Доступ ко всей JVM-экосистеме, развитое сообщество, активные обновления, хорошие инструменты разработки. Стартовая точка — официальный сайт и книга «Clojure for the Brave and True» Дэниела Хайгигагена (доступна онлайн бесплатно). Доступная среда разработки — Calva (плагин для VSCode) или CIDER (для Emacs).
Racket — если вы хотите учиться программированию или интересуетесь созданием языков. Великолепная IDE (DrRacket), отличная документация, огромный набор учебных материалов. SICP читается лучше всего именно на Racket. Если хотите изучать «How to Design Programs» — это тоже Racket.
SBCL (Steel Bank Common Lisp) — если хотите работать с классическим Common Lisp. Один из самых быстрых Lisp-компиляторов в мире, по производительности конкурирует с C. Среда разработки — Emacs + SLIME (или Sly). Книга для начала — «Practical Common Lisp» Питера Зайбеля (доступна онлайн бесплатно).
Emacs Lisp — если вы уже пользуетесь Emacs или собираетесь начать. Это не «универсальный» Lisp, а специализированный для редактирования. Но именно он сегодня — самый массово используемый Lisp-диалект в мире (благодаря миллионам активных пользователей Emacs).
Scheme через MIT/GNU Scheme или Chez Scheme — если вы хотите классический академический Scheme и работаете с SICP.
Hy — Lisp, компилируемый в Python AST. Любопытный гибрид: вы пишете S-выражения, но получаете доступ ко всем Python-библиотекам. Не для продакшена, но как способ попробовать Lisp, не отходя от Python — отличный.
Janet — современный минималистичный Lisp-вариант, написанный на C. Маленький, встраиваемый, без зависимостей.
Если же вы вообще не уверены, начинать ли — я бы рекомендовал прочитать хотя бы статью Пола Грэма «The Roots of Lisp» (33 страницы, читается за вечер). Грэм там показывает, как из семи примитивов Маккарти собирается вся машинерия Lisp, и одновременно — половина машинерии любого современного языка программирования. После этого Lisp перестаёт быть страшной и непонятной территорией.
15. Финал: язык, который продолжает учить
К 2026 году Lisp — это уже не модный, не «горячий» язык. На нём не делают стартапы для Y Combinator. О нём не говорят на каждой конференции. Поисковые запросы про Lisp стабильно низкие. Большинство современных программистов знают про него как про «что-то старое со скобками».
Но Lisp продолжает работать. На Clojure пишут банковские системы. На Emacs Lisp работает миллионы программистов. На Common Lisp до сих пор крутятся системы научной верстки, экспертные системы, инструменты для проектирования микросхем. На Scheme учат программированию в MIT, Брауне, Северо-Восточном университете. На Racket пишут новые языки программирования. На Guile живёт операционная система GNU Guix. Внутри Photoshop с конца 1980-х работал диалект AutoLisp (он же — внутри AutoCAD до сих пор); компьютерная алгебра Maxima — это потомок Macsyma — до сих пор использует Lisp под капотом. Это не язык, который умер. Это язык, который ушёл в инфраструктуру.
И главное — Lisp продолжает влиять. Каждое новое поколение языков программирования возвращается к чему-то, что в Lisp было с 1960-го. JavaScript получил функции первого класса. Python получил декораторы и pattern matching. Rust получил трейты и тип-классы (которые сами восходят к концепциям типизированных вариантов Lisp). Go получил горутины (которые соответствуют моделям параллелизма из EuLisp 1990 года). Каждый раз это представляется как новизна, и каждый раз Lisp-программисты тихо улыбаются.
Маккарти, придумавший Lisp в 1958 году, умер в 2011-м в возрасте 84 лет. Он успел увидеть Clojure, увидеть программистов всего мира, которые писали на нём системы, обрабатывающие миллионы транзакций. Сам Маккарти Lisp редко использовал в последние десятилетия жизни. Его интересы сместились в формальную логику и теорию знания — то есть в философию того, как человек что-то «знает» и как из этого знания делаются выводы. Но язык, который он придумал почти случайно, как побочный продукт работы по искусственному интеллекту, оказался одним из самых долгоживущих культурных артефактов нашей профессии.
Это не первая и, возможно, не последняя такая история в программировании, но она — одна из самых ярких. Идея, придуманная одним математиком за несколько месяцев в качестве удобной нотации, оказалась настолько удачной, что её до сих пор не превзошли по выразительной мощи. Вы только вдумайтесь — за шестьдесят восемь лет так и не превзошли!
Lisp — это, в каком-то смысле, главный аргумент в пользу того, что в программировании есть фундаментальные структуры, которые не зависят от моды, от железа, от языков. Маккарти их обнаружил. А мы их с тех пор продолжаем «изобретать» их заново под разными именами.
Дополнительное чтение
Главные тексты
- John McCarthy, «Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I», CACM, 1960 — исходная статья.
- Paul Graham, «The Roots of Lisp» (2002) — реконструкция Маккарти на современном языке. Обязательно к прочтению.
- Paul Graham, «Revenge of the Nerds» (2002) — манифест о том, почему Lisp даёт преимущество в реальной разработке.
- Paul Graham, «Beating the Averages» (2003) — как Lisp помог Виawebу обогнать конкурентов.
Книги
- «Structure and Interpretation of Computer Programs» (SICP) — Гарольд Эйбельсон и Джеральд Сассмен. Одна из лучших книг про программирование вообще.
- «Practical Common Lisp» — Питер Зайбель. Лучшая практическая книга про Common Lisp.
- «Clojure for the Brave and True» — Дэниел Хайгигагена. Лучшая книга для входа в Clojure.
- «On Lisp» — Пол Грэм. О макросах и продвинутом Lisp.
- «Let Over Lambda» — Даг Хойт. Глубокое погружение в макросы Common Lisp.
Видео
📺 Rich Hickey, «Simple Made Easy» — программный доклад автора Clojure о разнице между «простым» и «знакомым».
📺 Rich Hickey, «The Value of Values» — об иммутабельности и о том, что значит ценить значения.
📺 Rich Hickey, «Hammock-Driven Development» — о роли обдумывания в разработке.
📺 «The Most Beautiful Program Ever Written» — рассказ о функции eval в Scheme.
Среды разработки и реализации
- Clojure — основная страница.
- Calva — Clojure-плагин для VSCode.
- SBCL — главный современный Common Lisp.
- Racket — Scheme-подобная платформа для создания языков.
- MIT/GNU Scheme — классический Scheme.
- GNU Guile — встраиваемый Scheme.
История
- Herbert Stoyan, «LISP — History and Development» — академический обзор.
- The History of Lisp by John McCarthy — рассказ самого Маккарти.
- «The Evolution of Lisp» — Ричард Габриэль и Гай Стил, длинная история всех диалектов.
- The Symbolics Story — история эпохи Лисп-машин.