Экосистема и итог: ASDF, Quicklisp и сравнение диалектов
Экосистема и итог: ASDF и Quicklisp как система сборки и менеджер пакетов, обзор библиотек, и финальное сравнение Common Lisp с Scheme и Racket.
ASDF — стандартная система определения и сборки проектов Common Lisp (какие файлы и в каком порядке грузить); Quicklisp — менеджер библиотек, который скачивает и устанавливает зависимости одной командой.
Зачем это: от языка к практике
Мы прошли ядро Common Lisp — управление, макросы, структуры данных, CLOS, условия. Чтобы писать реальные программы, нужна ещё инфраструктура: как разбить проект на файлы и собрать его, как подключать чужие библиотеки, где их брать. Этим занимаются ASDF и Quicklisp — фактические стандарты экосистемы. А завершим курс честным сравнением трёх главных представителей семейства — Common Lisp, Scheme и Racket — чтобы вы понимали, какой диалект под какую задачу выбирать. Это финальный, обзорный урок: меньше нового синтаксиса, больше карты местности.
Стоит сразу снять психологический барьер, который останавливает многих при переходе от учебных упражнений к реальным проектам на Lisp. Со стороны кажется, что инфраструктура Lisp «странная» и незнакомая: нет привычного npm install или pip, образ живёт в памяти, сборка устроена иначе. На деле всё проще, чем выглядит: установили реализацию (обычно SBCL), поставили Quicklisp один раз, и дальше (ql:quickload "что-нужно") качает и грузит библиотеки, а свои проекты описываете маленьким .asd-файлом. Этого минимума хватает, чтобы начать писать настоящие программы уже сегодня. Цель урока — дать вам именно этот практический минимум плюс карту экосистемы, чтобы вы знали, что библиотеки есть и как их брать, и не отступили перед мнимой сложностью. А сравнение диалектов в конце поможет осознанно выбрать, на чём писать дальше, в зависимости от ваших задач.
ASDF: система сборки
Большой проект — это много файлов с зависимостями загрузки (макросы и пакеты должны грузиться раньше кода, который их использует). ASDF описывает это декларативно в файле .asd через систему (system) — единицу сборки с компонентами и их порядком.
;; myapp.asd — определение системы
(asdf:defsystem "myapp"
:description "Демо-приложение"
:version "0.1.0"
:depends-on ("alexandria" "cl-ppcre") ; зависимости-библиотеки
:components ((:file "packages") ; грузится первым
(:file "utils" :depends-on ("packages"))
(:file "core" :depends-on ("packages" "utils"))
(:file "main" :depends-on ("core"))))
;; загрузка системы (соберёт всё в правильном порядке):
;; (asdf:load-system "myapp")
ASDF строит граф зависимостей компонентов и грузит/компилирует их в корректном порядке, пересобирая только изменённое. Ключевое разделение (из прошлых уроков): пакет — это пространство имён символов, система ASDF — это единица сборки файлов. Обычно их именуют согласованно (пакет myapp и система "myapp"), но это разные сущности на ортогональных осях.
Quicklisp: менеджер библиотек
ASDF умеет собирать систему, но не скачивать зависимости. Это работа Quicklisp — менеджера, который держит курируемый набор тысяч библиотек и устанавливает их (с транзитивными зависимостями) одной командой ql:quickload.
;; установить и загрузить библиотеку (скачает при необходимости):
;; (ql:quickload "cl-ppcre") ; регулярные выражения
;; (ql:quickload "hunchentoot") ; веб-сервер
;; (ql:quickload "myapp") ; свой проект (если виден ASDF)
;; типичный старт сессии — загрузить нужное и работать в REPL:
;; (ql:quickload '("alexandria" "cl-ppcre" "local-time"))
На практике Quicklisp и ASDF работают в паре: вы пишете :depends-on в .asd, а (ql:quickload "myapp") подтягивает все зависимости из сети и затем собирает проект через ASDF. Это и есть стандартный рабочий процесс CL-разработчика. Для воспроизводимых сборок есть инструменты фиксации версий (Qlot, дистрибутивы Quicklisp по датам).
Карта экосистемы: что есть
Чтобы развеять миф «в Lisp нет библиотек», вот ориентиры зрелой экосистемы (все ставятся через Quicklisp): alexandria — утилиты-«стандартная библиотека номер два»; cl-ppcre — быстрые регулярные выражения; bordeaux-threads — переносимые потоки; hunchentoot/clack — веб-серверы; cl-json/jonathan — JSON; postmodern/mito — PostgreSQL и ORM (последний — на MOP); fiveam/parachute — тестирование; cffi — вызов C-кода. Реализаций тоже несколько: SBCL (наш основной — быстрый компилятор в нативный код), CCL, ECL (встраиваемая), ABCL (на JVM), Allegro/LispWorks (коммерческие, с IDE). Эта зрелость — результат десятилетий: ANSI-стандарт 1994 года жив, и код тех лет компилируется сегодня.
Финал: Common Lisp vs Scheme vs Racket
Три диалекта одного семейства — но с разной философией. Сведём различия, накопленные за курс, в карту выбора.
| Аспект | Common Lisp | Scheme | Racket |
| Философия | большой прагматичный «промышленный» язык | минимализм, academic, чистота | «язык для создания языков», богатая платформа |
| Размер стандарта | огромный (ANSI, ~1000 страниц) | крошечный (R7RS-small ~88 страниц) | большой, с обширной стандартной библиотекой |
| Пространства имён | Lisp-2 (функция и значение раздельно) | Lisp-1 (одно) | Lisp-1 |
| TCO | не гарантирован стандартом | обязателен | обязателен |
| Макросы | процедурные (defmacro), негигиеничные вручную | гигиеничные (syntax-rules/syntax-case) | гигиеничные, очень мощные (создание DSL/языков) |
| ООП | CLOS (мультиметоды, MOP) встроен | нет в ядре (библиотеки) | классы/объекты в библиотеке |
| Условия | система условий + рестарты | проще (зависит от реализации) | есть развитая система исключений |
| Ниша | большие прод-системы, AI-наследие, долгоживущий код | обучение, исследования, встраивание, R7RS-переносимость | обучение, языковые эксперименты, прикладной софт |
Как выбирать? Common Lisp — когда нужен мощный, стабильный, «всё включено» язык для серьёзных систем: богатый ООП (CLOS), исключительная обработка ошибок (рестарты), зрелые компиляторы (SBCL даёт скорость, близкую к C на числах), 30-летняя обратная совместимость. Scheme — когда ценны минимализм, чистота семантики, переносимость по стандарту и компактность (встраиваемые интерпретаторы, обучение принципам); его малое ядро — преимущество для тех, кто хочет понять язык «целиком». Racket — потомок Scheme, превратившийся в платформу: непревзойдённые средства создания собственных языков (#lang), богатая библиотека, отличная среда (DrRacket), что делает его прекрасным и для обучения, и для прикладной разработки, и для языковых исследований.
Что объединяет всё семейство
За различиями — общее ядро идей, ради которого стоит знать Lisp, даже работая на других языках. Гомоиконность (код = данные) и макросы дают непревзойдённую расширяемость языка. S-выражения — минимальный, единообразный синтаксис без «грамматического шума». Интерактивная разработка через REPL и образ — цикл «изменил-проверил» в реальном времени. Функции первого класса и замыкания (которые Lisp принёс в мейнстрим задолго до других). Динамическая, но прагматичная типизация с возможностью добавлять декларации для скорости. Эти идеи десятилетиями «утекали» в Python, JavaScript, Ruby, Clojure — поэтому, освоив Lisp, вы понимаете, откуда родом многое в современных языках, и обретаете способ мыслить о программах как о данных.
Завершая курс, стоит сказать, зачем вообще учить Lisp в эпоху, когда на работе чаще пишут на других языках. Ответ не в том, что вы непременно будете писать на Common Lisp в продакшене (хотя и это возможно — на нём работают реальные системы). Ответ в том, что Lisp меняет мышление. Пройдя через макросы, вы навсегда иначе смотрите на синтаксис — как на нечто пластичное, а не данное свыше. Поработав с CLOS, вы понимаете полиморфизм глубже, чем позволяет «методы в классе». Увидев условия и рестарты, вы замечаете ограниченность обычных исключений. Эти идеи делают вас сильнее на любом языке: вы узнаёте в чужих фичах знакомые лисповые корни, предвидите, куда эволюционируют языки, и проектируете чище. Многие великие программисты называли изучение Lisp «просветляющим опытом», который окупается всю карьеру, даже если строки на Lisp вы больше не напишете. Так что этот курс — не про один язык, а про способ думать о вычислениях, которому Lisp учит лучше всех.
Как работает под капотом
ASDF — это сам по себе CL-код (он написан на Common Lisp): defsystem создаёт объект-систему с компонентами, а операции load-op/compile-op обходят граф зависимостей и применяются к компонентам в топологическом порядке, пропуская актуальные (по времени модификации файлов). Quicklisp — это «дистрибутив»: индекс библиотек с их версиями и зависимостями; quickload разрешает транзитивные зависимости, скачивает недостающие архивы, регистрирует их в ASDF и делегирует ему сборку. Различия диалектов на уровне «под капотом» сводятся к их моделям: Lisp-2 хранит функцию и значение символа в разных ячейках (мы это разбирали), что влияет на компиляцию вызовов; обязательный TCO в Scheme/Racket требует от компилятора превращать хвостовые вызовы в переходы; гигиеничные макросы несут «синтаксические объекты» с контекстом, тогда как defmacro работает с голыми символами. Понимание этих первооснов — главный итог курса: вы видите не только «как писать», но и «почему язык так устроен».
Частые ошибки
- Путать ASDF и Quicklisp. ASDF собирает (порядок файлов), Quicklisp скачивает и устанавливает библиотеки. Они работают вместе, но это разные инструменты.
- Считать, что в Lisp мало библиотек. Quicklisp даёт тысячи зрелых библиотек; экосистема покрывает веб, БД, парсинг, тесты, FFI и т. д.
- Смешивать диалекты в голове. Lisp-2 (CL) против Lisp-1 (Scheme/Racket),
defmacroпротивsyntax-rules, гарантия TCO — это реальные различия; код одного диалекта не переносится в другой дословно. - Выбирать диалект по моде. CL — для больших стабильных систем, Scheme — для минимализма/обучения/встраивания, Racket — для языков и прикладного софта. Выбирайте под задачу.
- Не фиксировать версии зависимостей. Для воспроизводимых сборок используйте инструменты вроде Qlot/датированных дистрибутивов Quicklisp, а не «последнее доступное».
Итоги
- ASDF — стандартная система сборки CL:
defsystemописывает компоненты и порядок загрузки. - Quicklisp — менеджер библиотек:
ql:quickloadскачивает и ставит зависимости; работает в паре с ASDF. - Экосистема зрелая: alexandria, cl-ppcre, hunchentoot, postmodern/mito, fiveam, cffi; реализации — SBCL и другие.
- Common Lisp — большой прагматичный язык (CLOS, условия, скорость, совместимость); Scheme — минимализм и переносимость; Racket — платформа для создания языков.
- Ключевые различия: Lisp-2 vs Lisp-1,
defmacrovs гигиеничные макросы, гарантия TCO, встроенный CLOS. - Общее ядро семейства — гомоиконность, макросы, S-выражения, REPL, функции первого класса — идеи, повлиявшие на всё современное программирование.