Введение

Стандарт языка программирования Common Lisp (CL) — это диалект языка программирования Lisp, опубликованный в стандарте ANSI INCITS 226 1994 (S2018) (ранее X3.226 1994 (R1999)). Common Lisp HyperSpec, версия в формате HTML с гиперссылками, является производной от стандарта ANSI Common Lisp. Язык Common Lisp был разработан как стандартизированный и улучшенный преемник Maclisp. К началу 1980-х годов несколько групп уже работали над различными преемниками MacLisp: Lisp Machine Lisp (также известный как ZetaLisp), Spice Lisp, NIL и S 1 Lisp. Common Lisp был создан для объединения, стандартизации и расширения возможностей этих диалектов MacLisp. Common Lisp — это не реализация, а спецификация языка. Доступно несколько реализаций стандарта Common Lisp, включая программное обеспечение с открытым исходным кодом и проприетарные продукты. Common Lisp — это язык программирования общего назначения, поддерживающий несколько парадигм. Он сочетает в себе процедурное, функциональное и объектно-ориентированное программирование. Как динамический язык программирования, он упрощает эволюционную и инкрементальную разработку программного обеспечения с итеративной компиляцией в эффективные программы во время выполнения. Эта постепенная разработка часто выполняется интерактивно, не прерывая работу приложения. Он также поддерживает необязательную аннотацию типов и приведение типов, которые могут быть добавлены по мере необходимости на этапах профилирования и оптимизации, чтобы компилятор мог генерировать более эффективный код. Например, fixnum может хранить целое число без распаковки в диапазоне, поддерживаемом аппаратным обеспечением и реализацией, что обеспечивает более эффективные арифметические операции, чем с большими целыми числами или типами произвольной точности. Аналогичным образом, компилятор может быть настроен для каждого модуля или функции, чтобы указать желаемый уровень безопасности, используя объявления об оптимизации. Common Lisp включает CLOS, объектную систему, поддерживающую мультиметоды и комбинации методов. Она часто реализуется с использованием протокола метаобъектов. Common Lisp расширяем благодаря стандартным возможностям, таким как макросы Lisp (преобразования кода) и макросы читателя (анализаторы ввода для символов). Common Lisp обеспечивает частичную обратную совместимость с Maclisp и оригинальным Lisp Джона Маккарти, что позволяет переносить старое программное обеспечение Lisp на Common Lisp.

История

Работа над Common Lisp началась в 1981 году после инициативы менеджера ARPA Боба Энгельмора по разработке единого общепринятого диалекта Lisp. Большая часть первоначального проектирования языка осуществлялась по электронной почте. В 1982 году Гай Л. Стил-младший представил первый обзор Common Lisp на Симпозиуме ACM 1982 года по LISP и функциональному программированию. Первая документация по языку была опубликована в 1984 году под названием Common Lisp the Language (известная как CLtL1), первое издание. Второе издание (известное как CLtL2), опубликованное в 1990 году, включало множество изменений в языке, внесенных в ходе процесса стандартизации ANSI Common Lisp: расширенный синтаксис LOOP, объектную систему Common Lisp (Common Lisp Object System), систему условий для обработки ошибок, интерфейс к pretty printer и многое другое. Однако CLtL2 не описывает окончательный стандарт ANSI Common Lisp и, следовательно, не является документацией по стандарту ANSI Common Lisp. Окончательный стандарт ANSI Common Lisp был опубликован в 1994 году. С тех пор никаких обновлений стандарта не выпускалось. Различные расширения и улучшения Common Lisp (например, поддержка Unicode, многопоточность, ввод-вывод на основе CLOS) были реализованы в различных реализациях и библиотеках.

Типы данных

Common Lisp имеет много типов данных.

Скалярные типы

Типы чисел включают целые числа, рациональные числа, числа с плавающей точкой и комплексные числа. Common Lisp использует бигнумы для представления числовых значений произвольного размера и точности. Тип рационального числа точно представляет дроби, что недоступно во многих языках. Common Lisp автоматически преобразует числовые значения между этими типами по мере необходимости. Тип символов Common Lisp не ограничивается символами ASCII. Большинство современных реализаций поддерживают символы Unicode. Тип символа является общим для языков Lisp, но малоизвестен за их пределами. Символ – это уникальный именованный объект данных, состоящий из нескольких частей: имя, значение, функция, список свойств и пакет. Среди них наиболее важными являются ячейка значения и ячейка функции. Символы в Lisp часто используются аналогично идентификаторам в других языках: для хранения значения переменной; однако существует множество других способов их применения. Обычно, при вычислении символа возвращается его значение. Некоторые символы вычисляются сами по себе, например, все символы в пакете ключевых слов являются самовычисляемыми. Булевы значения в Common Lisp представлены самовычисляемыми символами T и NIL. Common Lisp имеет пространства имен для символов, называемые «пакетами». Существует ряд функций для округления скалярных числовых значений различными способами. Функция `round` округляет аргумент до ближайшего целого числа, при этом значения, находящиеся на границе, округляются до ближайшего четного целого числа. Функции `truncate`, `floor` и `ceiling` округляют в сторону нуля, вниз и вверх соответственно. Все эти функции возвращают отброшенную дробную часть в качестве дополнительного значения. Например, `(floor 2.5)` возвращает -3, 0.5; `(ceiling 2.5)` возвращает -2, -0.5; `(round 2.5)` возвращает 2, 0.5; и `(round 3.5)` возвращает 4, -0.5.

Структуры данных

Типы последовательностей в Common Lisp включают списки, векторы, битовые векторы и строки. Существует множество операций, которые могут работать с любым типом последовательности. Как и почти во всех других диалектах Lisp, списки в Common Lisp состоят из консов, иногда называемых ячейками консов или парами. Конс — это структура данных с двумя полями, называемыми car и cdr. Список — это цепочка консов или пустой список. Поле car каждого конса указывает на элемент списка (возможно, на другой список). Поле cdr каждого конса указывает на следующий конс, за исключением последнего конса в списке, поле cdr которого указывает на nil. Консы также могут быть легко использованы для реализации деревьев и других сложных структур данных, хотя обычно рекомендуется использовать структуры или экземпляры классов. Также возможно создавать циклические структуры данных с помощью консов. Common Lisp поддерживает многомерные массивы и может динамически изменять размер изменяемых массивов при необходимости. Многомерные массивы могут использоваться для матричных вычислений. Вектор — это одномерный массив. Массивы могут содержать элементы любого типа (даже смешанные типы в одном массиве) или могут быть специализированы для хранения элементов определенного типа, например, вектор битов. Обычно поддерживается лишь несколько типов. Многие реализации могут оптимизировать функции массивов, когда используется массивы с заданной специализацией типа. Два типа специализированных массивов являются стандартными: строка — это вектор символов, а битовый вектор — это вектор битов. Хеш-таблицы хранят соответствия между объектами данных. Любой объект может использоваться в качестве ключа или значения. Хеш-таблицы автоматически изменяют свой размер по мере необходимости. Пакеты — это коллекции символов, используемые главным образом для разделения частей программы на пространства имен. Пакет может экспортировать некоторые символы, помечая их как часть публичного интерфейса. Пакеты могут использовать другие пакеты. Структуры, аналогичные структурам в C и записям в Pascal, представляют собой произвольные сложные структуры данных с любым количеством и типом полей (называемых слотами). Структуры поддерживают одиночное наследование. Классы похожи на структуры, но предлагают более динамические возможности и множественное наследование. (См. CLOS). Классы были добавлены в Common Lisp относительно поздно, и существует некоторое концептуальное пересечение со структурами. Объекты, созданные на основе классов, называются экземплярами. Особый случай представляют собой обобщенные функции. Обобщенные функции являются одновременно функциями и экземплярами.

Пространство имен функций

Пространство имен для функций отделено от пространства имен для переменных данных. Это ключевое различие между Common Lisp и Scheme. Для Common Lisp операторы, определяющие имена в пространстве имен функций, включают defun, flet, labels, defmethod и defgeneric. Чтобы передать функцию по имени в качестве аргумента другой функции, необходимо использовать специальный оператор функции, обычно сокращенно #'. Первый пример сортировки, приведенный выше, относится к функции, имя которой задано символом > в пространстве имен функций, и обозначается как #'>. И наоборот, для вызова функции, переданной таким образом, следует использовать оператор funcall, применяемый к аргументу. Модель вычислений Scheme проще: существует только одно пространство имен, и все позиции в выражении вычисляются (в любом порядке) – не только аргументы. Поэтому код, написанный на одном диалекте, иногда может быть непонятен программистам, более опытным в другом. Например, многие программисты Common Lisp предпочитают использовать описательные имена переменных, такие как list или string, что может вызвать проблемы в Scheme, поскольку они будут локально скрывать имена функций. Вопрос о том, является ли раздельное пространство имен для функций преимуществом, является предметом споров в сообществе Lisp. Обычно это называют дискуссией Lisp 1 против Lisp 2. Lisp 1 относится к модели Scheme, а Lisp 2 – к модели Common Lisp. Эти названия были введены в 1988 году в статье Ричарда П. Габриэля и Кента Питмана, в которой подробно сравниваются два подхода.

Область применения

Как и программы во многих других языках программирования, программы на Common Lisp используют имена для обращения к переменным, функциям и многим другим типам сущностей. Именованные ссылки подчиняются области видимости. Связь между именем и сущностью, на которую это имя ссылается, называется привязкой. Область видимости определяет набор условий, при которых имя получает определенную привязку.

Глобальный

Некоторые окружения в Lisp являются глобально доступными. Например, если определён новый тип, он становится известным везде впоследствии. Обращения к этому типу ищут его в этом глобальном окружении.

Общая система объектов Lisp (CLOS)

Common Lisp включает в себя инструментарий для объектно-ориентированного программирования – Common Lisp Object System или CLOS. Питер Норвиг объясняет, что многие шаблоны проектирования проще реализовать в динамическом языке с возможностями CLOS (множественное наследование, миксины, мультиметоды, метаклассы, комбинации методов и т.д.). Было предложено несколько расширений Common Lisp для объектно-ориентированного программирования для включения в стандарт ANSI Common Lisp, но в конечном итоге CLOS был принят в качестве стандартной объектной системы для Common Lisp. CLOS – это динамическая объектная система с множественной диспетчеризацией и множественным наследованием, радикально отличающаяся от средств ООП, представленных в статических языках, таких как C++ или Java. Как динамическая объектная система, CLOS позволяет вносить изменения в общие функции и классы во время выполнения. Методы можно добавлять и удалять, классы можно добавлять и переопределять, объекты можно обновлять при изменении классов, а также изменять класс объектов. CLOS интегрирована в ANSI Common Lisp. Общие функции могут использоваться как обычные функции и являются данными первого класса. Каждый класс CLOS интегрирован в систему типов Common Lisp. Многие типы Common Lisp имеют соответствующий класс. Существует больший потенциал для использования CLOS в Common Lisp. В спецификации не указано, реализованы ли условия с использованием CLOS. Имена файлов и потоки могут быть реализованы с помощью CLOS. Эти дополнительные возможности использования CLOS для ANSI Common Lisp не входят в стандарт. Фактические реализации Common Lisp используют CLOS для имён файлов, потоков, ввода-вывода, условий, реализации самой CLOS и многого другого.

Компилятор и интерпретатор

Интерпретатор Lisp напрямую выполняет исходный код Lisp, представленный в виде объектов Lisp (списки, символы, числа и т.д.), читаемых из s-выражений. Компилятор Lisp генерирует байт-код или машинный код из исходного кода Lisp. Common Lisp позволяет компилировать как отдельные функции Lisp в памяти, так и компилировать целые файлы во внешне хранимый компилированный код (файлы fasl). Некоторые реализации более ранних диалектов Lisp предоставляли как интерпретатор, так и компилятор. К сожалению, часто семантика была различной. В этих ранних Lisps лексическая область видимости реализовывалась в компиляторе, а динамическая область видимости — в интерпретаторе. Common Lisp требует, чтобы как интерпретатор, так и компилятор использовали лексическую область видимости по умолчанию. Стандарт Common Lisp описывает семантику интерпретатора и компилятора. Компилятор можно вызвать, используя функцию `compile` для отдельных функций и функцию `compile-file` для файлов. Common Lisp позволяет указывать типы и предоставляет способы влиять на политику генерации кода компилятором. Для этого различные параметры оптимизации могут принимать значения от 0 (неважно) до 3 (наиболее важно): скорость, объем памяти, безопасность, отладка и скорость компиляции. Существует также функция для вычисления кода Lisp: `eval`. `eval` принимает код в виде предварительно проанализированных s-выражений, а не, как в некоторых других языках, в виде текстовых строк. Таким образом, код можно построить с помощью обычных функций Lisp для создания списков и символов, а затем вычислить этот код с помощью функции `eval`. Некоторые реализации Common Lisp (например, Clozure CL и SBCL) реализуют `eval`, используя свой компилятор. Таким образом, код компилируется, даже если он вычисляется с помощью функции `eval`. Файловый компилятор вызывается с помощью функции `compile-file`. Сгенерированный файл с компилированным кодом называется файлом fasl (от "fast load"). Эти файлы fasl, а также файлы исходного кода можно загрузить с помощью функции `load` в работающую систему Common Lisp. В зависимости от реализации, файловый компилятор генерирует байт-код (например, для виртуальной машины Java), код на языке C (который затем компилируется с помощью компилятора C) или непосредственно машинный код. Реализации Common Lisp можно использовать интерактивно, даже если код полностью компилируется. Таким образом, концепция интерпретируемого языка неприменима к интерактивному Common Lisp. Язык различает время чтения, время компиляции, время загрузки и время выполнения и позволяет пользовательскому коду также учитывать эти различия для выполнения необходимого типа обработки на нужном этапе. Некоторые специальные операторы предоставляются специально для интерактивной разработки; например, `defvar` присвоит значение своей предоставленной переменной только в том случае, если она еще не была связана, в то время как `defparameter` всегда выполнит присваивание. Это различие полезно при интерактивной оценке, компиляции и загрузке кода в работающем образе. Предоставляются также некоторые средства для помощи в написании компиляторов и интерпретаторов. Символы состоят из объектов первого уровня и непосредственно манипулируются пользовательским кодом. Специальный оператор `progv` позволяет создавать лексические связи программным путем, а пакеты также поддаются манипулированию. Компилятор Lisp доступен во время выполнения для компиляции файлов или отдельных функций. Это позволяет легко использовать Lisp в качестве промежуточного компилятора или интерпретатора для другого языка.

Сравнение с другими Lisps

Common Lisp чаще всего сравнивают и противопоставляют Scheme, если только потому, что это два самых популярных диалекта Lisp. Scheme предшествует Common Lisp и происходит не только из той же традиции Lisp, но и от некоторых тех же разработчиков — Гай Стил, который вместе с Джеральдом Джеем Суссманом разработал Scheme, возглавлял комитет по стандартам Common Lisp. Common Lisp — это язык программирования общего назначения, в отличие от вариантов Lisp, таких как Emacs Lisp и AutoLISP, которые являются языками расширения, встроенными в конкретные продукты (GNU Emacs и AutoCAD соответственно). В отличие от многих более ранних Lisps, Common Lisp (как и Scheme) по умолчанию использует лексическую область видимости переменных как для интерпретируемого, так и для компилируемого кода. Большинство систем Lisp, чьи разработки внесли вклад в Common Lisp, — таких как ZetaLisp и Franz Lisp, — использовали динамически области видимости переменных в своих интерпретаторах и лексически области видимости переменных в своих компиляторах. Scheme ввёл в Lisp исключительное использование лексически области видимости переменных, вдохновлённое ALGOL 68. Common Lisp поддерживает динамически области видимости переменных, но они должны быть явно объявлены как «специальные». Нет различий в области видимости между интерпретаторами и компиляторами ANSI Common Lisp. Common Lisp иногда называют Lisp 2, а Scheme — Lisp 1, ссылаясь на использование Common Lisp отдельных пространств имён для функций и переменных. (На самом деле, Common Lisp имеет множество пространств имён, таких как те, что используются для go-меток, имён блоков и ключевых слов циклов). Существует давняя полемика между сторонниками Common Lisp и Scheme относительно компромиссов, связанных с использованием нескольких пространств имён. В Scheme (в общем случае) необходимо избегать присвоения переменным имён, конфликтующих с функциями; функции Scheme часто имеют аргументы с именами lis, lst или lyst, чтобы не конфликтовать с системной функцией list. Однако в Common Lisp необходимо явно ссылаться на пространство имён функции при передаче функции в качестве аргумента, что также является распространённой практикой, как в примере сортировки выше. Common Lisp также отличается от Scheme в обработке булевых значений. Scheme использует специальные значения #t и #f для представления истинности и ложности. Common Lisp следует более старой конвенции Lisp об использовании символов T и NIL, причём NIL также обозначает пустой список. В Common Lisp любое значение, отличное от NIL, рассматривается как истинное в условных выражениях, таких как if, в то время как в Scheme все значения, отличные от #f, рассматриваются как истинные. Эти конвенции позволяют некоторым операторам в обоих языках служить как предикатами (отвечая на вопрос, имеющий булево значение), так и возвращать полезное значение для дальнейших вычислений, но в Scheme значение ' (цитата), которое эквивалентно NIL в Common Lisp, оценивается как истинное в булевом выражении. Наконец, стандарты Scheme требуют оптимизации хвостовых вызовов, чего не требует стандарт Common Lisp. Большинство реализаций Common Lisp предлагают оптимизацию хвостовых вызовов, хотя часто только тогда, когда программист использует директиву оптимизации. Тем не менее, общепринятый стиль кодирования Common Lisp не поощряет повсеместное использование рекурсии, которое предпочитает стиль Scheme — то, что программист Scheme выразил бы с помощью хвостовой рекурсии, пользователь Common Lisp обычно выражает итеративным выражением в do, dolist, loop или (недавно) с помощью пакета iterate.

Коммерческие реализации

Allegro Common Lisp для Microsoft Windows, FreeBSD, Linux, Apple macOS и различных вариантов UNIX. Allegro CL предоставляет интегрированную среду разработки (IDE) (для Windows и Linux) и широкие возможности для развертывания приложений. Liquid Common Lisp, ранее известная как Lucid Common Lisp. Поддерживается только техническое обслуживание, новых выпусков нет. LispWorks для Microsoft Windows, FreeBSD, Linux, Apple macOS, iOS, Android и различных вариантов UNIX. LispWorks предоставляет интегрированную среду разработки (IDE) (доступную для большинства платформ, но не для iOS и Android) и широкие возможности для развертывания приложений. mocl для iOS, Android и macOS. Open Genera для DEC Alpha. Scieneer Common Lisp, разработанная для высокопроизводительных научных вычислений.

Свободно перераспределяемые реализации

Armed Bear Common Lisp (ABCL) – реализация CL, работающая на виртуальной машине Java. Включает компилятор в байт-код Java и обеспечивает доступ к библиотекам Java из CL. Ранее являлась компонентом Armed Bear J Editor. Clasp – реализация на основе LLVM, беспрепятственно взаимодействующая с библиотеками C++. Работает на нескольких Unix и Unix-подобных системах (включая macOS). CLISP – реализация с компиляцией в байт-код, переносимая и работающая на нескольких Unix и Unix-подобных системах (включая macOS), а также на Microsoft Windows и ряде других систем. Clozure CL (CCL) – изначально свободный и открытый форк Macintosh Common Lisp. Как следует из истории, CCL был разработан для Macintosh, но Clozure CL теперь работает на macOS, FreeBSD, Linux, Solaris и Windows. Поддерживаются 32- и 64-битные порты x86 для каждой платформы. Также имеются порты Power PC для Mac OS и Linux. Ранее CCL был известен как OpenMCL, но это название больше не используется, чтобы избежать путаницы с версией Macintosh Common Lisp с открытым исходным кодом. CMUCL – изначально разработан в Университете Карнеги-Меллона, теперь поддерживается как свободное программное обеспечение с открытым исходным кодом группой добровольцев. CMUCL использует быстрый компилятор машинного кода. Доступен для Linux и BSD на Intel x86; Linux для Alpha; macOS на Intel x86 и PowerPC; и Solaris, IRIX и HP UX на их родных платформах. Corman Common Lisp – для Microsoft Windows. В январе 2015 года Corman Lisp был опубликован под лицензией MIT. Embeddable Common Lisp (ECL) – включает интерпретатор и компилятор байт-кода. Также может компилировать код Lisp в машинный код через компилятор C. ECL компилирует код Lisp в C, затем компилирует код C с помощью компилятора C и может загрузить полученный машинный код. Также возможно встраивать ECL в программы на C и код на C в программы Common Lisp. GNU Common Lisp (GCL) – компилятор Lisp проекта GNU. Пока не полностью соответствует стандарту ANSI, однако является предпочтительной реализацией для нескольких крупных проектов, включая математические инструменты Maxima, AXIOM и (исторически) ACL2. GCL работает на Linux под одиннадцатью различными архитектурами, а также под Windows, Solaris и FreeBSD. Macintosh Common Lisp (MCL) – версия 5.2 для компьютеров Apple Macintosh с процессором PowerPC под управлением Mac OS X имеет открытый исходный код. RMCL (на основе MCL 5.2) работает на компьютерах Apple Macintosh с Intel, используя бинарный транслятор Rosetta от Apple. ManKai Common Lisp (MKCL) – ответвление от ECL. MKCL делает акцент на надежности, стабильности и общем качестве кода благодаря сильно переработанной, изначально многопоточной системе времени выполнения. На Linux MKCL имеет полностью совместимую с POSIX систему времени выполнения. Movitz – реализует среду Lisp для компьютеров x86 без использования какой-либо базовой операционной системы. Poplog – реализует версию CL, с POP 11, и опционально Prolog и Standard ML (SML), позволяя смешанное программирование. Для всех языком реализации является POP 11, который компилируется инкрементально. Также имеет интегрированный редактор, подобный Emacs, который взаимодействует с компилятором. Steel Bank Common Lisp (SBCL) – ответвление от CMUCL. "В общем, SBCL отличается от CMU CL большим вниманием к поддерживаемости". SBCL работает на тех же платформах, что и CMUCL, за исключением HP/UX; кроме того, он работает на Linux для AMD64, PowerPC, SPARC, MIPS, Windows x86 и имеет экспериментальную поддержку для Windows AMD64. SBCL по умолчанию не использует интерпретатор; все выражения компилируются в машинный код, если пользователь не включит интерпретатор. Компилятор SBCL генерирует быстрый машинный код, согласно предыдущей версии The Computer Language Benchmarks Game. Ufasoft Common Lisp – порт CLISP для платформы Windows с ядром, написанным на C++.