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

История языка Lisp

Разработчику Архитектору

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…


История языка 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), где и был впервые сформулирован термин "искусственный интеллект". Реализация заняла ещё несколько лет: рекурсия и динамическое выделение памяти требовали от ЭВМ и рантайма непривычной организации вычислений.

Маккарти обходит это ограничение, предложив интерпретатор, написанный вручную на ассемблере для 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 System). 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 лет эмпирического опыта. Черновики и книги по языку появились в 1980-х; стандарт ANSI X3.226-1994 утверждён в 1994 году. Спецификация включала:

  • единый, богатый набор встроенных типов — символы, числа (целые, рациональные, с плавающей точкой, комплексные), последовательности (списки и векторы), хэш-таблицы, структуры, потоки;
  • объектную систему CLOS (Common Lisp Object System), разработанную в конце 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 data 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 (ML, Haskell); с Lisp сильнее связаны REPL и метапрограммирование, чем статический вывод типов.
  • 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, DSL и генеративного программирования эта идея остаётся практичной.

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 остаётся ориентиром.


Как история помогает в практике

Исторический обзор Lisp полезен не только "для кругозора". Он объясняет, почему в современных проектах на Common Lisp или Clojure вы увидите те же идеи, что и в ранних работах:

  • сильный упор на REPL и пошаговую сборку решения;
  • программирование через преобразование структур данных, а не через длинные цепочки состояний;
  • расширение языка под задачу (макросы, DSL), а не только выбор библиотеки.

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


Что особенно важно вынести новичку

Если брать минимум из этой статьи для старта в языке:

  1. Lisp не "старый ради старого" — он старый и при этом методологически современный.
  2. Макросы, REPL и функции первого класса — базовые рабочие инструменты.
  3. Диалекты отличаются, но ядро мышления общее — выражения, рекурсия, композиция, интерактивность.

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


В подборках

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

ИсторияИстория языка Fortran, История языка Pascal, История языка COBOL, История языка visual-basic, История языка Lua, История ассемблерных языков.