История языка Lisp
История языка Lisp
1. Место Lisp в истории программирования
Язык Lisp (от LISt Processor) занимает уникальное положение в истории программирования: он один из старейших языков высокого уровня, сохранивших активное применение, и наиболее последовательная реализация идей, заложенных в основания математической логики и теории вычислений. Его появление в 1958 году ознаменовало переход от императивных моделей вычислений, ориентированных на машинную архитектуру фон Неймана, к декларативной парадигме, в которой программа рассматривается не как последовательность команд процессору, а как выражение преобразований над структурами данных. Lisp стал первым языком, в котором синтаксис был полностью выведен из семантики: код и данные выражаются в единой, рекурсивно определяемой структуре — связном списке, — что открывает путь к глубокой интроспекции и метапрограммированию.
Lisp возник как инструмент формализации интеллектуальных процессов — в рамках исследований в области искусственного интеллекта, предпринятых Джоном Маккарти и его коллегами в Массачусетском технологическом институте (MIT). Таким образом, Lisp изначально концептуализировался не как средство автоматизации рутинных задач, а как язык мыслей, пригодный для моделирования рассуждений — и эта философская установка определила его архитектурные особенности на десятилетия вперёд.
2. Предыстория
Чтобы понять генезис Lisp, необходимо обратиться к событиям, предшествовавшим его созданию на два десятилетия. В 1930-х годах Алонзо Чёрч предложил лямбда-исчисление — формальную систему, предназначенную для описания функций и их аппликации. В отличие от машин Тьюринга, фокусировавшихся на механических шагах вычисления, лямбда-исчисление оперировало абстрактными преобразованиями: переменная связывалась, выражение подставлялось, редукция выполнялась до нормальной формы. Эта система оказалась эквивалентной по вычислительной мощности машине Тьюринга (теорема Чёрча–Тьюринга), но её выразительность была ближе к математической практике, чем к архитектуре ЭВМ.
Одновременно с этим в работах Курта Гёделя и Стефана Клини развивалась теория рекурсивных функций, где центральную роль играли примитивная и частичная рекурсия. Именно рекурсия, а не итерация, стала естественным способом определения вычислений в теоретических моделях.
Маккарти, будучи знакомым с этими работами, стремился создать практический язык, в котором:
- функции были бы первоклассными объектами (могли передаваться как аргументы, возвращаться как результаты);
- программы строились бы рекурсивно, без опоры на изменяемое состояние;
- данные и код имели бы единое представление, допускающее динамическое построение и анализ выражений.
Эти требования вытекали низ попытки реализовать вычислительную модель, адекватную человеческому рассуждению — в рамках проекта по автоматическому доказательству теорем и игре в шахматы.
3. Создание: 1958–1960 годы
Первая версия Lisp была разработана Джоном Маккарти совместно с Марвином Мински, Аланом Ньюэллом и другими участниками Dartmouth Summer Research Project on Artificial Intelligence (1956), где и был впервые сформулирован термин «искусственный интеллект». Однако реализация задержалась: на тот момент не существовало компиляторов, способных обрабатывать рекурсивные вызовы, а стек вызовов ещё не был общепринятой техникой (его введение в практику приписывают работе Дейкстры 1960 года).
Маккарти обходит это ограничение, предложив интерпретатор, написанный вручную на ассемблере для IBM 704. Этот интерпретатор (иногда именуемый Lisp I) поддерживал лишь базовые конструкции:
- символы (атомы) — неизменяемые именованные сущности;
- списки — упорядоченные пары
(car . cdr), гдеcar— первый элемент,cdr— «остаток» списка; - семь примитивных функций:
atom,eq,car,cdr,cons,cond,lambda.
Замечательно, что уже в этой первой версии присутствовала поддержка анонимных функций через lambda — заимствование напрямую из лямбда-исчисления Чёрча. Вызов функции записывался в префиксной нотации: (f x y), что гарантировало однозначный синтаксический разбор без приоритетов операций.
Спецификация cond (условное выражение) позволяла определять рекурсивные функции, такие как факториал или вычисление чисел Фибоначчи, без использования циклов — в полном соответствии с теоретико-рекурсивной парадигмой. Маккарти продемонстрировал, что с помощью cond и lambda можно выразить любое вычисление, реализуемое машиной Тьюринга.
В 1960 году Маккарти публикует статью «Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I» — первый формальный отчёт о Lisp. Несмотря на отсутствие второй части (оставшейся ненаписанной), эта работа считается основополагающей. В ней впервые вводится понятие вычисления как символьного преобразования, где программа — выражение, подлежащее редукции.
4. Ранняя эволюция: Lisp 1.5 и становление экосистемы
К 1962 году появляется Lisp 1.5 — первая широко распространённая версия, разработанная Стивеном Расселом, Даниэлем Эдвардсом и другими под руководством Маккарти. В отличие от интерпретатора 1958 года, Lisp 1.5 включал:
- компилятор (хотя и примитивный), переводящий
lambda-выражения в машинный код; - механизм динамической области видимости (в отличие от лексической, которая появится позже);
- встроенную поддержку символьных вычислений, включая
quoteиeval.
Именно в Lisp 1.5 впервые реализуется функция eval — интерпретатор Lisp, написанный на самом Lisp. Это событие имеет фундаментальное значение: оно демонстрирует рефлексивность языка — способность к самоописанию. Функция (eval expr) принимает на вход список, представляющий выражение, и выполняет его как программу. Тем самым граница между данными и кодом становится условной: любой список потенциально исполняем; любая программа — это просто структура данных, ожидающая eval.
С появлением eval и quote возникает макросистема в зачаточной форме: программист может строить выражения во время выполнения, манипулируя ими как списками, а затем передавать результат eval. Хотя полноценные синтаксические макросы (как в Common Lisp) появятся лишь в 1970-х, уже в начале 1960-х исследователи MIT и Стэнфорда активно использовали этот механизм для создания domain-specific languages (DSL) внутри Lisp — например, для описания правил в экспертных системах.
К середине 1960-х Lisp становится основным языком исследований в области ИИ. На нём реализуются:
- SAINT (Symbolic Automatic INTegrator) — система символьного интегрирования (1961);
- STUDENT — программа решения текстовых задач по алгебре (1964);
- SHRDLU — система понимания естественного языка в ограниченной «мире блоков» (1968–1970).
Эти проекты подтверждают гипотезу Маккарти: символьные манипуляции через рекурсивные функции действительно пригодны для моделирования когнитивных процессов.
5. Дивергенция: MIT, Stanford, и рождение диалектов
К концу 1960-х происходит фрагментация Lisp-сообщества по институциональным и философским линиям.
В MIT развивается Maclisp — мощный, но нестандартизованный диалект, ориентированный на производительность и интеграцию с операционной системой ITS (Incompatible Timesharing Система). Maclisp вводит ряд нововведений: хешированные символы, компиляцию в объектный код, поддержку чисел с плавающей точкой и комплексных чисел. Он становится основой для таких систем, как MACSYMA — первой крупной системой компьютерной алгебры.
Одновременно в Стэнфорде Джон Маккарти работает над Stanford Lisp, стремясь к большей строгости и логической чистоте. Позже его ученики — Гай Стил и Джералд Сассман — разрабатывают Scheme (1975), который станет радикальной переработкой Lisp в духе лямбда-исчисления.
Scheme вводит:
- лексическую область видимости (в отличие от динамической в Maclisp и Lisp 1.5);
- единое пространство имён функций и переменных («Lisp-1», в отличие от «Lisp-2», где функции и значения раздельны — как в Common Lisp);
- хвостовую рекурсию как итерацию: любой хвостовой вызов оптимизируется в безстековый переход, что делает рекурсию эффективной заменой циклам;
- минимализм специальных форм: только
lambda,if,define,quote,set!,begin.
Scheme становится языком обучения (например, в учебнике Structure and Interpretation of Computer Programs, 1985), подчёркивая, что язык программирования — это средство формулирования идей.
6. Стандартизация и Common Lisp
К середине 1970-х в экосистеме Lisp существовало более двух десятков несовместимых реализаций: Maclisp, Interlisp (развиваемый в Xerox PARC и Беркли), Lisp Machine Lisp (на базе специализированных ЭВМ от Symbolics и LMI), NIL (New Implementation of Lisp), а также Scheme и его варианты. Отсутствие переносимости тормозило развитие библиотек и промышленное внедрение. В 1981 году по инициативе DARPA (Управления перспективных исследовательских проектов Министерства обороны США) была запущена работа по созданию единого стандарта. Координацией занималась Special Interest Group on Lisp (SIGLISP) при ACM; главным редактором спецификации стал Гай Стил — автор The Hacker’s Dictionary и соавтор Scheme.
Результатом стал Common Lisp — попытка систематизировать 25 лет эмпирического опыта. Спецификация Common Lisp, опубликованная в 1984 году как ANSI X3.226-1994 (окончательно утверждена в 1994), представляла собой:
- единый, богатый набор встроенных типов: символы, числа (целые, рациональные, с плавающей точкой, комплексные), последовательности (списки и векторы), хэш-таблицы, структуры, потоки;
- объектную систему CLOS (Common Lisp Object Система), разработанную в конце 1980-х и включённую в стандарт. CLOS отличался от классических ООП-моделей: он поддерживал множественное наследование, обобщённые функции (generic functions), методы, диспетчеризуемые по любому аргументу (multimethods), и метаклассы. Это делало CLOS ближе к подходам в prototype-based языках и современным системам вроде Julia, чем к Java или C++;
- мощную систему макросов, основанную на шаблонах (macroexpand-time pattern matching), а не на простой текстовой подстановке. Макросы Common Lisp работают на уровне s-выражений и запускаются до компиляции, что позволяет создавать синтаксические конструкции, неотличимые от встроенных;
- условную систему, основанную на условных выражениях (
cond,case) и обобщённой системе обработки ошибок (conditions and restarts), позволяющей не просто перехватывать исключения, но и предлагать возобновления (restarts) — стратегии восстановления, выбираемые динамически в точке возникновения ошибки.
Common Lisp закреплял динамическую природу Lisp: компилятор мог вызываться во время выполнения (compile, compile-file), функции перекомпилировались «на лету», а отладчик (SLDB — Superior Lisp Debugger) предоставлял интерактивный доступ к стеку вызовов с возможностью модификации кода и данных в реальном времени. Эта интерактивность — архитектурный принцип: среда программирования рассматривалась как единый, изменяемый организм.
Реализации Common Lisp (например, SBCL, Clozure CL, Allegro CL, CLISP) до сих пор используются в нишевых, но критически важных областях: авионика (Boeing, Lockheed Martin), финансовые системы (ITA Software, позднее приобретённая Google), робототехника (NASA), а также в системах, требующих длительного апгрейда без остановки (hot code swapping).
7. Lisp и эпоха специализированных машин
В 1970–1980-х годах развитие Lisp напрямую стимулировало создание Lisp-машин — аппаратных платформ, оптимизированных под выполнение Lisp-кода. Компании Symbolics и Lisp Machines Inc. (LMI) выпускали рабочие станции, в которых:
- указатели были тегированными: 2–3 бита в каждом машинном слове указывали тип данных (символ, список, число, функция и т.д.), что позволяло реализовать динамическую типизацию без накладных расходов;
- сборка мусора была аппаратно ускорена;
- микрокод процессора включал инструкции для
car,cdr,cons,apply; - вся операционная система (Genera у Symbolics) была написана на Lisp — включая графический интерфейс, сетевой стек, редактор и отладчик.
Это был апогей идеи Маккарти: полная интеграция языка и среды. Однако к концу 1980-х рост производительности универсальных процессоров (в частности, RISC-архитектур) сделал Lisp-машины экономически невыгодными. Рынок сместился в сторону Unix-совместимых систем и языков C/C++. Lisp утратил статус «языка будущего» в массовом сознании, но сохранил влияние на уровне идей.
8. Современные воплощения: Clojure и его философия
В 2007 году Рич Хикки представляет Clojure — диалект Lisp, изначально созданный для Java Virtual Machine (JVM). Clojure нельзя рассматривать как «ещё один Lisp»: это реинтерпретация принципов Lisp в контексте многопоточности, неизменяемости и практического промышленного применения.
Ключевые идеи Clojure:
- неизменяемые структуры данных по умолчанию: векторы, списки, множества, отображения реализованы как persistent Данные structures с эффективным разделением памяти (structural sharing). Модификация создаёт новую версию, не затрагивая исходную — это устраняет гонки данных без блокировок;
- модель параллелизма через ссылки:
ref,atom,agent,var— абстракции, явно разделяющие идентичность, состояние и значение. Например,atomобеспечивает атомарное обновление состояния с помощью функции-трансформера;refработает в рамках программных транзакций (STM — Software Transactional Memory); - хост-интеграция: Clojure не стремится заменить экосистему хоста. На JVM он использует весь стек Java: библиотеки, инструменты сборки (Maven, Leiningen), профайлеры. Аналогично, ClojureScript компилируется в JavaScript и интегрируется с React, Node.js и т.д.;
- макросы с гигиеническим контролем: хотя синтаксис макросов близок к Common Lisp, Clojure добавляет механизмы предотвращения захвата имён (gensym по умолчанию,
syntax-quote, auto-gensym), повышая безопасность метапрограммирования; - отказ от лексем с побочными эффектами в «чистом» коде: вызовы вроде
(println …)явно выделяются (хотя и не запрещены), поощряя функциональный стиль.
Clojure — синтез: он берёт гомогенность синтаксиса и выразительность макросов из Lisp, строгую типизацию и компиляцию — из ML-семейства (через типовые подсказки), а модель параллелизма — из исследований по конкурирующим системам (например, работы Лесли Лэмпорта). Его успех в компаниях уровня Walmart, Nubank, CircleCI свидетельствует: принципы Lisp остаются жизнеспособными при условии адаптации к современным ограничениям — прежде всего, к задачам масштабируемости и сопровождаемости.
9. Влияние Lisp на другие языки: Haskell, ML, и за их пределами
Lisp оказал косвенное, но глубокое влияние на развитие функциональных языков, хотя и не является их прямым предком (этой роли придерживается лямбда-исчисление).
-
Haskell унаследовал от Lisp идею выражений как основной единицы программы, а также использование рекурсии вместо итерации. Однако, в отличие от Lisp, Haskell строго разделяет чистые вычисления и побочные эффекты (через монады), что делает его ближе к теоретической чистоте Чёрча, чем к прагматизму Маккарти. Тем не менее, система Template Haskell — это метапрограммирование, вдохновлённое лисповскими макросами: код генерируется во время компиляции через исполнение Haskell-кода.
-
ML-семейство (Standard ML, OCaml, F#) заимствовало у Lisp:
- интерактивную среду (REPL), ставшую обязательной для функциональных языков;
- автоматическое управление памятью (сборка мусора), впервые реализованную в Lisp 1.5;
- полиморфные типы и вывод типов — хотя формально эта идея возникла в системе Hindley–Milner, её практическая реализация была проверена на Lisp-подобных системах (например, в Nuprl и LISP/ML).
-
JavaScript и Python: несмотря на императивную основу, оба языка включают элементы, прямо восходящие к Lisp:
- функции как объекты первого класса;
- замыкания (lexical closures), реализованные в Scheme и перенесённые в JavaScript через влияние Scheme-разработчиков (например, Брендана Эйха);
eval— хотя и считается антипаттерном, его существование — прямая дань лисповской рефлексивности.
-
Rust, несмотря на системную направленность, использует макросы на основе синтаксических деревьев (
macro_rules!, declarative macros), а не текстовой подстановки — подход, прямо заимствованный из Scheme и Common Lisp. Даже термин hygiene в документации Rust ссылается на гигиенические макросы Scheme.
Таким образом, Lisp — не столько «язык, который все забыли», сколько невидимая основа: его идеи, очищенные от специфики синтаксиса, пронизывают современные языки через концепции, ставшие общепринятыми.
10. Lisp как методология мышления
Язык Lisp выжил благодаря гомоморфизму между структурой данных и структурой программы. Эта особенность позволяет рассматривать программу не как статический артефакт, а как динамическую модель, которую можно анализировать, преобразовывать и расширять в процессе выполнения. В эпоху, когда доминируют компонентные архитектуры, конфигурации через код (Infrastructure as Code), генеративное программирование и LLM-augmented Разработка, эта особенность приобретает новую актуальность.
Lisp не предполагает, что программист знает всё заранее. Он предлагает среду для постепенного уточнения: сначала — прототип на интерпретаторе, затем — макросы, формирующие DSL, затем — компиляция в эффективный код, затем — интеграция с хост-системой. Такой цикл ближе к научному методу, чем к инженерному производству.
Маккарти писал: «Lisp was not the product of a committee, nor was it designed to meet a list of requirements. It came out of a theory of computation». Сегодня, когда теория вычислений сталкивается с реальностью распределённых систем, нейросетевых моделей и квантовых алгоритмов, эта теоретическая основа — необходимость. И в этом смысле Lisp остаётся ориентиром.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Благодаря homoiconicity, Lisp предоставляет уникальную возможность создания языков внутри языка. Макросы в Lisp — это не просто текстовые замены, как в некоторых других системах. Они работают на… В Lisp всё выражается через списки. Список — это рекурсивная структура, состоящая из атомов и других списков. Атом представляет собой базовую единицу данных — число, символ, строку или логическое… Типизация, набор правил определения типа данных значений языка. Эта особенность позволяет Lisp сохранять чистоту функционального подхода даже при наличии явного управления потоком. Программа на Lisp воспринимается как древовидная структура выражений, каждое из… Форма defun состоит из трёх обязательных компонентов — имени функции — символа, который становится глобальным идентификатором, списка параметров — последовательности символов, представляющих входные… Гайд по установке и настройке с написанием первой программы и её запуском. Функциональное программирование в Lisp проявляется через центральную роль функций как первоклассных объектов. Каждая функция в Lisp — это значение, которое можно передавать как аргумент, возвращать… Все программы на Lisp записываются в виде S-выражений (symbolic expressions). S-выражение — это либо атом, либо список.Основы языка Lisp
Архитектура Lisp-систем
Типы данных в Lisp
Управляющие конструкции и операторы Lisp
Функции и рекурсия в Lisp
Первая программа на Lisp
Функциональное программирование в Lisp
Справочник по Lisp