Экосистема и итог: 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 LispSchemeRacket
Философиябольшой прагматичный «промышленный» языкминимализм, 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, defmacro vs гигиеничные макросы, гарантия TCO, встроенный CLOS.
  • Общее ядро семейства — гомоиконность, макросы, S-выражения, REPL, функции первого класса — идеи, повлиявшие на всё современное программирование.
Проверьте себя
1. Чем различаются роли ASDF и Quicklisp?
AЭто два названия одного инструмента
BASDF описывает сборку проекта (какие файлы и в каком порядке грузить), а Quicklisp скачивает и устанавливает библиотеки-зависимости
CQuicklisp собирает проект, ASDF пишет код
DASDF — это компилятор, Quicklisp — отладчик
2. Какое из утверждений верно отражает различие диалектов?
ACommon Lisp гарантирует TCO, а Scheme нет
BCommon Lisp — это Lisp-2 (раздельные ячейки функции и значения) с негигиеничными defmacro, а Scheme/Racket — Lisp-1 с гигиеничными макросами и обязательным TCO
CВ Scheme встроен CLOS, а в Common Lisp нет ООП
DRacket не поддерживает макросы