Paradigm | Мультипарадигма : процедурный, функциональный, объектно-ориентированный, мета, отражающий, общий |
---|---|
Семейство | Lisp |
Разработано | Скоттом Фалманом, Ричардом П. Габриэлем, Дэвидом А. Мун, Кентом Питманом, Гай Стил, Дэн Вайнреб |
Разработчик | ANSI X3J13 комитет |
Впервые появился | 1984 (36 лет назад) (1984), 1994 (26 лет назад) (1994) для ANSI Common Lisp |
Дисциплина ввода | Динамический, сильный |
Область действия | Лексический, необязательно динамический |
OS | Кросс-платформенный |
Расширения имен файлов | .lisp,.lsp,.l,.cl,.fasl |
Веб-сайт | common-lisp.net |
Основные реализации | |
Allegro CL, ABCL, CLISP, Clozure CL, CMUCL, ECL, GCL, LispWorks, SBCL, Символика Common Lisp | |
Диалекты | |
CLtL1, CLtL2, ANSI Common Lisp | |
Под вашей | |
Lisp, Lisp Machine Lisp, Maclisp, Схема, Interlisp | |
Под регионом | |
Clojure, Dylan, Emacs Lisp, EuLisp, ISLISP, * Lisp, AutoLisp, Julia, Moose, R, SKILL, SubL |
Common Lisp (CL) - диалект языка программирования Lisp, опубликованный в стандартном документе ANSI ANSI INCITS 226-1994 [S2008] (ранее X3.226-1994 (R1999)). Common Lisp HyperSpec, версия HTML с гиперссылками, была получена из стандарта ANSI Common Lisp.
Language Common Lisp был разработан как стандартизованный и улучшенный преемник Maclisp. К 1980-х несколько групп уже работали над разнообразными преемниками MacLisp: Lisp Machine Lisp (также известный как ZetaLisp), Spice Lisp, NIL и С-1 Лисп. Common Lisp стремился унифицировать, стандартизировать и расширить возможности этих диалектов MacLisp. Common Lisp - это не реализация, а спецификация языка . Доступно несколько реализаций стандарта Common Lisp, включая бесплатное программное обеспечение с открытым исходным кодом и проприетарные продукты. Common Lisp - это универсальный язык программирования с использованием парадигмами. Он поддерживает комбинацию процедурного, функционального и объектно-ориентированного программирования парадигм. Как язык динамического программирования, он облегчает эволюционную и инкрементную программу программного обеспечения с итеративной компиляцией в эффективные программы времени выполнения. Эта инкрементальная разработка часто выполняется в интерактивном режиме без прерывания работы приложения.
Он также поддерживает дополнительные аннотации, которые могут быть добавлены при необходимости на более поздних этапах профилирования и оптимизации, чтобы компилятор мог генерировать более эффективный код. Например, fixnum
может содержать распакованное целое число в диапазоне, поддерживаемом аппаратом и реализацией, что позволяет выполнять более эффективную арифметику, чем для больших целых чисел или типов произвольной точности. Точно так же компилятору можно указать для каждого модуля или функции, какой тип уровня безопасности требуется, с помощью объявлений оптимизации.
Common Lisp включает CLOS, объектную систему, которая поддерживает мультиметоды и комбинации методов. Часто это реализуется с помощью протокола Метаобъект.
Common Lisp расширяется с помощью стандартных функций, таких как мак Lisp (преобразование кода) и макросы чтения (анализаторы ввода для символов).
Common Lisp обеспечивает частичную обратную совместимость с Maclisp и оригинальным Lisp Джона Маккарти. Это позволяет переносить старое программное обеспечение Lisp на Common Lisp.
Работа над общим Lisp начался в 1981 году после менеджера ARPA Боба Энгельмора по разработке единого стандартного диалекта Lisp сообщества. Большая часть первоначального языкового дизайна была сделана по электронной почте. В 1982 году Гай Л. Стил младший сделал первый обзор Common Lisp на симпозиуме ACM 1982 года по LISP и функциональному программированию.
Первая языковая документация была опубликована в 1984 году как Common Lisp the Language (известный как CLtL1), первое издание. Второе издание (известное как CLtL2), опубликованное в 1990 году, включило в себя множество изменений языка, внесенных в процесс стандартизации ANSI Common Lisp: расширенный синтаксис LOOP, объектная система Common Lisp, система условий для обработки ошибок, интерфейс для симпатичный принтер и многое другое. Но CLtL2 не является окончательным стандартным ANSI Common Lisp и, следовательно, не является документацией по ANSI Common Lisp. Окончательный стандарт ANSI Common Lisp был опубликован в 1994 году. С тех пор никаких обновлений стандарта не публиковалось. Различные расширения и улучшения Common Lisp (например, Unicode, Concurrency, ввод-вывод на основе CLOS) были предоставлены реализациями и библиотеками.
Common Lisp - это диалект Lisp. Он использует S-выражения для обозначения как кода, так и структуры данных. Вызов функций, макроформы и специальные формы записываются в виде списков с именем оператора первым, как в этих примерах:
(+ 2 2); складывает 2 и 2, получая 4. Имя функции - «+». В Лиспе операторов как таковых.
(defvar * x *); Гарантирует, что переменная * x * существует,; не придавая ему значения. Звездочки являются частью; имя, по соглашению обозначающее специальную (глобальную) переменную. ; Символ * x * также наделяется своим: последующие привязки являются скорее динамическими, чем лексическими. (setf * x * 42.1); Устанавливает переменную * x * в значение с плавающей точкой 42.1
;; Определите функцию, возводящую число в квадрат: (defun square (x) (* x x))
;; Выполните функцию: (квадрат 3); Возвращает 9
;; Конструкция пусть создает область для локальных чисел. Вот ;; переменная 'a' привязана к 6, а переменная 'b' привязана ;; до 4. Внутри 'пусть находится' тело ', в котором возвращается последнее вычисленное значение. ;; Здесь результат a и b возвращается из сложения выражения let. ;; Переменные a и b имеют лексическую область видимости, если только символы не были ;; помечены как специальные переменные (например, предыдущим DEFVAR). (пусть ((a 6) (b 4)) (+ a b)); возвращает 10
Common Lisp имеет много типов данных.
Числовые типы включают целые числа, отношения, числа с плавающей запятой и комплексные числа. Common Lisp использует bignums для представления числовых значений произвольного размера и точности. Тип отношения точно представляет дроби, что не доступно на многих языках. Common Lisp автоматически сопоставляет числовые значения между этим типами по мере необходимости.
Тип символа Common Lisp не ограничен символами ASCII. Большинство современных реализаций позволяют использовать символы Unicode.
Тип символов является общим для языков Lisp, но в степени неизвестен за их пределами. Символ - это уникальный именованный объект данных, состоящий из нескольких частей: имя, значение, функция, список свойств и пакет. Из них наиболее важными являются ячейка значения и ячейка функции. Символы в Лиспе часто используются аналогично используемому языку: для хранения значений переменных; однако есть много других применений. Обычно при символе символа возвращается его значение. Некоторые символы оцениваются сами по себе, например, все символы в пакете ключевыми словами являются самооценочными ключевыми словами. Логические значения в Common Lisp представляют символами самооценки T и NIL. В Common Lisp есть пространство имен для символов, называемые «пакетами».
Существует ряд функций для округления скалярных числовых переменных способов. Функция round
округляет аргумент до ближайшего целого числа, промежуточные округляются до четного целого числа. Функции усекают
, пол
и потолок
округляют в сторону нуля, вниз или вверх соответственно. Все эти функции возвращают отброшенную дробную часть как вторичное значение. Например, (этаж -2,5)
дает -3, 0,5; (потолок -2,5)
дает −2, −0,5; (округление 2,5)
дает 2, 0,5; и (раунд 3,5)
дает 4, -0,5.
Типы последовательностей в Common Lisp списки включают, класс битовые инструкции и строки. Есть много операций, которые могут работать с любым типом работает.
Как и почти во всех других диалектах Лиспа, списки в Common Lisp состоят из conses, иногда называемых cons-ячейками или парами. Минусы - это структура данных с двумя слотами, которые называются car и cdr. Список - это связанная цепочка заключений или пустой список. Автомобиль каждого минуса относится к члену списка (возможно, другого списка). Cdr каждого потребителя относится к следующим минусам, за исключением последних минусов в списке, cdr которого относится к значению nil
. Conses также можно легко использовать для реализации деревьев и других структур данных; обычно рекомендуется использовать вместо этого экземпляры хотя структуры или класса. Также возможно создать круговые структуры данных с помощью conses.
Common Lisp поддерживает многомерные массивы и при необходимости может динамически изменять размер настраиваемых массивов. Для матричной математики можно использовать многомерные массивы. Вектор - это одномерный массив. Массивы могут нести в качестве элементов любой тип (даже смешанные типы в одном массиве) или специализированы для содержания элементов определенного типа, как в векторе битов. Обычно поддерживаются только несколько типов. Многие могут оптимизировать функции массива, когда массив зависит от типа. Стандартными являются два типа набора массивов: строка - это вектор символов, а битовый вектор - это вектор из бит.
Хеш-таблицы хранят ассоциации между объектами данных. Любой город может Роман как ключ или значение. Размер хеш-таблиц автоматически изменяется по мере необходимости.
Пакеты - это наборы символов, используемые в основном для разделения частей программы на пространства имен. Пакет может экспортировать некоторые символы, помечая их как часть общедоступного интерфейса. Пакеты могут использовать другие пакеты.
Структуры, аналогичные по использованию структурым C и надеж Pascal, произвольные сложные структуры данных с любыми и типом полей (называемых слотами). Структуры допускают одиночное наследование.
Классы похожи на структуры, но предоставляют более динамические функции и множественное наследование. (См. ЗАКРЫТЬ ). Классы были добавлены в Common Lisp поздно, и есть концептуальное совпадение со структурой. Объекты, созданные из классов, называются экземплярами. Особый случай - универсальные функции. Общие функции - это как функции, так и экземпляры.
Common Lisp поддерживает функции первого класса. Например, можно писать функции, которые также принимают другие функции в качестве аргументов или возвращают функции. Это позволяет описывать самые общие операции.
Библиотека Common Lisp в степени полагается на такие функции высшего порядка. Например, функция sort
принимает реляционный оператор в качестве аргумента и ключевую функцию в качестве необязательного аргумента ключевого слова. Это можно использовать не только для сортировки любого типа данных, но и для сортировки структур данных по ключу.
;; Сортирует список с помощью>и < function as the relational operator. (sort (list 5 2 6 3 1 4) #'>); Возвращает (6 5 4 3 2 1) (sort (list 5 2 6 3 1 4) # '<) ; Returns (1 2 3 4 5 6)
;; Сортирует список в соответствии с первым каждым подсписка. (Sort (list' (9 A) '(3 B) '(4 C)) #' < :key #'first) ; Returns ((3 B) (4 C) (9 A))
Модель оценки для функций очень проста. Когда оценщик встречает форму (f a1 a2...)
, он предполагает, что символ с именем f является одним из следующих :
лямбда
.Если f - имя, то оцениваются аргументы a1, a2,..., в порядке слева направо, и функция будет найдена и вызвана с теми же значениями, которые указаны в параметрах параметров.
Макрос defun
определяет функции, в которых определение функции дает имя функции, имена любых аргументов и тела функции:
(defun square ( x) (* xx))
Определения функций м огут компилятора директивы компилятора, как известные объявления, которые включают компилятору параметры оптимизации или типах данных аргументов. Они также могут использовать строки (строки документации), которые могут использовать Lisp для интерактивной документации:
(defun square (x) «Вычисляет квадрат x с одинарным числом с плавающей запятой». (Declare (single-float x) ( optimize (speed 3) (debug 0) (security 1))) (single-float (* xx)))
Анонимные функции (функциональные литералы ) с помощью лямбда
выражения, например (lambda (x) (* xx))
для функции, возводящей в квадрат свой аргумент. Стиль программирования Lisp часто использует функции высшего порядка, для которых используются анонимные функции в качестве аргументов.
Локальные функции могут быть использованы с помощью flet
и меток
.
(flet ((square (x) (* xx))) (квадрат 3))
Там - это несколько других операторов, связанных с определением функций и управлением ими. Например, функция может быть скомпилирована с помощью оператора compile
. (По умолчанию запускают функции с использованием интерпретатора, если не указано, другие функции компилируют).
Макрос defgeneric
определяет универсальные функции. Универсальные функции - это набор методов. Макрос defmethod
определяет методы.
Методы могут специфицировать свои параметры на стандартные классах CLOS, системные классах, классах структуры или отдельных объектов. Для многих типов существующих системных классов.
Когда вызывается универсальная функция, множественная отправка определит эффективный метод использования.
(defgeneric add (ab))
(defmethod add ((a number) (b number)) (+ ab))
(defmethod add ((a vector) (b number)) (map ' vector (lambda (n) (+ nb)) a))
(defmethod add ((a vector) (b vector)) (map 'vector #' + ab))
(defmethod add ((строка) (b строка)) (объединить 'строку ab))
(добавить 2 3); возвращает 5 (добавить # (1 2 3 4) 7); возвращает # (8 9 10 11) (добавить # (1 2 3 4) # (4 3 2 1)); возвращает # (5 5 5 5) (добавить «ОБЩИЙ» «LISP»); возвращает "COMMON LISP"
Универсальные функции также являются типом данных первого класса. У вас больше возможностей, чем описано выше.
Пространство имен для имен функций отделено от пространства имен для чисел данных. Это различие между Common Lisp и Scheme. Для Common Lisp операторы, определяющие имена в именах функций, включают defun
, flet
, labels
, defmethod
и defgeneric
.
Чтобы передать функцию по имени в качестве аргумента другой функции, необходимо использовать специальный оператор function
, обычно обозначаемый как # '
. Первый пример sort
выше относится к функциям, названной символом >
в пространстве имен функций, с кодом #'>
. И наоборот, чтобы вызвать функцию, переданную таким образом, можно использовать оператор funcall
для аргумента. Модель
схемы проще: существует только одно пространство имен, и все позиции в форме оцениваются (в любом порядке), а не только аргументы. Поэтому код, написанный на одном диалекте, иногда сбивает с толку программистов, более опытных в другом. Например, многие программисты Common Lisp любят использовать описательные имена чисел, такие как список или строки, которые могут вызвать проблемы в схеме, поскольку они локально затенять имена функций.
То, является ли отдельное пространство имен для функций преимуществом, является разногласий в сообществе Lisp. Обычно это называют спором Лисп-1 против Лисп-2. Lisp-1 относится к модели Схема, а Lisp-2 относится к модели Common Lisp. Эти названия были придуманы в статье 1988 г. Ричардом П. Гэбриэлем и Кентом Питманом, в котором подробно сравниваются два подхода.
Common Lisp поддерживает концепцию множественных значений, где любое выражение всегда имеет одно первичное значение, но может также иметь любое количество вторичных значений, которые могут быть получены и проверены заинтересованными вызывающими сторонами. Эта концепция отличается от возврата значения списка, поскольку вторичные значения являются полностью необязательными и передаются через выделенный побочный канал. Это означает, что вызывающие абоненты могут оставаться в полном неведении о наличии вторичных значений, если они им не нужны, и это делает удобным использование механизма передачи информации, которая иногда бывает полезной, но не всегда необходимой. Например,
TRUNCATE
округляет заданное число до целого в сторону нуля. Однако он также возвращает остаток в качестве вторичного значения, что позволяет очень легко определить, какое значение было усечено. Он также поддерживает необязательный параметр делителя, который можно тривиально использовать для евклидова деления :(let ((x 1266778) (y 458)) (multi-value-bind (частное остаток) ( truncate xy) (format nil "~ A, деленное на ~ A ~ A остаток ~ A" xy частный остаток))) ;;;; =>«1266778, деленное на 458, составляет 2765, остаток 408»
GETHASH
возвращает значение ключа в ассоциативной карте или значение по умолчанию в противном случае, и вторичное логическое значение, указывающее, был найден. Таким образом, код, который не заботится о том, было ли значение найдено или предоставлено по умолчанию, может просто использовать его как есть, но когда такое различие важно, онможет вторичное проверить логическое значение и отреагировать соответствующим образом. Оба варианта использования поддерживаются одним и тем же вызовом, и ни один из них не обременен и не ограничен другим. Наличие этой функции на уровне языка избавляет от необходимости проверять наличие ключа или сравнивать его с null, как это было бы на других языках.(defun get-answer (library) (gethash 'библиотека ответов 42)) (defun the-answer-1 (library) (format nil «Ответ ~ A» (библиотека get-answer))) ;;;; Возвращает «Ответ 42», если ANSWER отсутствует в БИБЛИОТЕКЕ (defun the-answer-2 (library) (multi-value-bind (answer sure-p) (get-answer library) (if (not sure-p) ») Я не знаю "(формат ноль" Ответ ~ A "ответ)))) ;;;; Возвращает" Я не знаю ", если ОТВЕТ отсутствует в БИБЛИОТЕКЕ
Несколько преимуществ поддерживаются стандартными формами, наиболее распространенными из которых являются MULTIPLE -VALUE-BIND
специальная форма для доступа к вторичным значениям и VALUES
для нескольких значений возврата:
(defun magic-8-ball () «Вернуть прогноз прогноза с вероятностью в качестве вторичного значения »(значения« Outlook хороший »(случайный 1,0))) ;;;; =>" Outlook хороший ";;;; =>0.3187
Другие типы данных в Common Lisp Включает:
Подобно программам на многих других языках программирования, программы Common Lisp используют имена для обозначения для чисел, функций и других типов сущностей. Именованные ссылки зависят от области применения.
Связь между именем и сущностью, на которую указывает имя, называется привязкой.
Область действия относится к набору обстоятельств, при которых определено, что имя имеет определенную привязку.
Обстоятельства, определяющие область действия в Common Lisp, включая:
(go x)
означает передачу управления метке x
, тогда как (print x)
относится к переменной x
. Обе области x
могут быть активны в одной и той же области текста программы, поскольку метки тела тега находятся в пространстве имен, отличном от имен. Специальная форма или макроформа имеет полный контроль над значениями всех символов в ее синтаксисе. Например, в (defclass x (ab) ())
, определение класса, (ab)
- это список базовых классов, поэтому эти имена ищутся в имен классов, а x
не является подключенным к существующей привязке, а является именем нового класса, производного от a
и b
. Эти факты вытекают исключительно из семантики defclass
. Единственный общий факт об этом выражении - то, что defclass
относится к привязке макроса; все остальное зависит от defclass
.x
заключена в конструкцию привязки, такую как let
, которая определяетку для x
, тогда ссылка находится в области, созданной привязкой.понять, на что указанный символ, программист Common Lisp должен знать, какой тип ссылок выражается, какую область видимости он использует, если он - Чтобы это ссылка на переменную (динамическая или лексическая область видимости), а также ситуация во время выполнения: в какой среде разрешается ссылка, где была введена привязка в среду и т. д.
Некоторые среды в Lisp распространяются глобально. Например, если новый тип определен, он будет известен после этого. Ссылки на этот тип ищите в этой глобальной среде.
Одним из типов среды в Common Lisp является динамическая среда. Привязки, установленные в этой среде, имеют динамическую протяженность, что означает привязка к началу выполнения некоторой конструкции, как блок let
, и исчезает, когда эта конструкция завершает выполнение: ее время жизни привязано к динамической активации и деактивации блока. Однако динамическая привязка не только видна внутри этого блока; он также виден всем функциям, вызываемым из этого блока. Этот тип видимости известен как неопределенная область видимости. Привязки, которые демонстрируют динамическую протяженность (время жизни, связанное с активацией и деактивацией блока) и неопределенную область действия (видимую для всех функций, вызываемых из этого блока), как говорят, имеют динамическую область действия.
Common Lisp поддерживает переменные с динамической областью видимости, которые также называются специальными переменными. Некоторые другие виды привязок также имеют динамическую область видимости, например, перезапуск и теги перехвата. Привязки функций не могут быть динамически ограничены с помощью flet
который (обеспечивает только объекты привязки функций с лексической областью видимости), но функции (объект первого уровня в Common Lisp) могут быть назначены переменным с динамической областью видимости, привязанным с С помощью let
в динамической области, вызывается с помощью funcall
или APPLY
.
. Динамическая область полезна, потому что она ссылочную ясность и дисциплину к глобальным переменным. В информативном глобальном переменном неодобрении как потенциальным источником ошибок, потому что они создают специальные, скрытые каналы связи между модулями, нежелательным и неожиданным взаимодействием.
В Common Lisp переменная, имеющая привязку только верхнего уровня, ведет так же, как глобальная переменная на других языках программирования. В нем можно сохранить новое значение, и это значение просто заменяет то, что находится в привязке верхнего уровня. Неосторожная замена глобальных значений, связанных с ошибками, вызванными использованием глобальных чисел. Однако другой способ работы со специальной вставкой - Установите ей новую локальную привязку внутри выражения. Иногда это «повторным связыванием» называют стандартный. Привязка динамической памяти областью видимости создается новое место в памяти. Пока эта привязка работает, все ссылки на эту переменную связь к новой привязке; предыдущая привязка скрыта. Когда выполнение выражения привязки завершается, временная ячейка исчезает, и открывается старая привязка с неизменным исходным значением. Конечно, несколько динамических привязок для одной и той же модели вложенных.
Реализации Common Lisp, которые включают многопоточность, динамические области видимости специфичны для каждого потока выполнения. Таким образом, специальные переменные абстракции для локального хранилища потоков. Если один поток повторно связывает специальную переменную, это повторное связывание не влияет на эту переменную в других потоках. Значение, хранящееся в привязке, может быть получено только потоком, создавшим эту привязку. Если каждый поток связывает какую-то специальную переменную * x *
, тогда * x *
ведет себя как локальное хранилище потока. Среди потоков, которые не повторно связывают * x *
, он ведет себя как обычный глобальный: все эти потоки связаны на одну и ту же привязку верхнего уровня * x *
.
Могут and динамические переменные для Неявно передается дополнительная функция контекста, которая не появляется в качестве дополнительного расширения функций. Это особенно полезно, когда передача управления через уровни несвязанного кода. В такой ситуации обычно требуется глобальная переменная. Эта глобальная переменная должна быть сохранена и восстановлена, чтобы схема не нарушалась при рекурсии: об этом позаботится повторное сопоставление динамической переменной. И эта схема должна быть сообщена об этом сообщении.
В библиотеке Common Lisp есть много стандартных специальных чисел. Например, все стандартные потоки ввода-вывода хранятся в привязках верхнего уровня хорошо известного числа. Стандартный поток выводится в * стандартном выводе *.
Предположим, функция foo записывает в стандартный вывод:
(defun foo () (формат t «Hello, world»))
Чтобы записать свой вывод в виде строки символов, * стандартный вывод * может быть привязан к строковому потоку и вызван:
(with-output-to-string (* standard-output *) (foo))
->"Hello, world"; собранные выходные данные возвращаются в виде строки
Common Lisp поддерживает лексические среды. Формально привязки в лексической среде имеют лексическую область действия и могут иметь неопределенную или динамическую степень, в зависимости от типа пространства имен. Лексическая область действия означает, что видимость физически ограничена блоком, в котором установлена привязка. Ссылки, которые не встроены в этот блок текстуально (т. Е. Лексически), просто не видят эту привязку.
Теги в TAGBODY имеют лексическую область видимости. Выражение (GO X) ошибочно, если оно не встроено в TAGBODY, которое содержит метку X. Привязки меток исчезают, когда TAGBODY завершает свое выполнение, потому что они имеют динамическую протяженность. Если этот блок повторно вводится при вызове лексического замыкания , для тела этого замыкания недопустимо пытаться передать управление тегу через GO:
(defvar * stashed *) ;; будет функция (содержит tagbody (setf * stashed * (lambda () (go some-label))) (go end-label) ;; пропустить (print "Hello") some-label (print "Hello") end-label) ->NIL
Когда TAGBODY выполняется, он сначала оценивает формулу, которая выполняет функцию в новой * включенной *. Затем (go end-label) передает управление конечной метке, пропуская код (выведите «Hello»). Конечная метка находится в конце тела тега, тега завершается, давая NIL. Предположим, что теперь вызывается ранее запомненная функция:
(funcall * stashed *) ;; Ошибка!
Эта ситуация ошибочная. Ответом одной реализации является состояние ошибки, содержащее сообщение «GO: tagbody для тега SOME-LABEL уже оставлено». Функция попыталась оценить (go some-label), который лексически встроен в тело тега, и преобразуется в метку. Однако tagbody не выполняется (его размер закончился), и поэтому передача управления не может произойти.
Связывания локальных функций в Lisp имеют лексическую область видимости, а привязки переменных по умолчанию также имеют лексическую область видимости. В отличие от лейблов GO, оба они имеют неограниченный размер. Когда устанавливается привязка лексической функции или переменной, эта привязка продолжает существовать до тех пор, пока на нее возможны ссылки, даже после завершения конструкции, установившей эту привязку. Ссылки на лексические переменные и функции после завершения их устанавливающей конструкции возможны благодаря лексическим замыканиям.
Лексическое связывание является режимом связывания по умолчанию для переменных Common Lisp. Для отдельного символа его можно переключить в динамическую область видимости либо локальным объявлением, либо глобальным объявлением. Последнее может происходить неявно посредством использования таких конструкций, как DEFVAR или DEFPARAMETER. Важным условием программирования на Common Lisp является то, что специальные (т.е. динамически ограниченные) переменные имеют имена, которые начинаются и заканчиваются звездочкой сигилом *
в так называемом «соглашении наушников ». При соблюдении этого соглашения создается отдельное пространство имен для специальных переменных, так что переменные, которые должны быть лексическими, не могут быть случайно сделаны специальными.
Лексическая область полезна по нескольким причинам.
Во-первых, ссылки на переменные и функции могут быть скомпилированы в эффективный машинный код, потому что структура среды выполнения относительно просто. Во многих случаях можно оптимизировать хранение в стеке, поэтому открытие и закрытие лексических областей имеет минимальные накладные расходы. Даже в тех случаях, когда необходимо создать полное закрытие, доступ к среде закрытия эффективным способом; обычно каждая переменная становится смещением в векторе привязки, и поэтому ссылка на переменную становится простой инструкцией по сохранению смещения .
Во-второй, лексическая область видимости (в сочетании с неопределенной степенью) дает подняться до лексического замыкания, которое, в свою очередь, создает целую парадигму программирования, основанную вокруг использования функций, являющихся объектами первого класса, что лежит в основе функционального программирования.
Третьи, возможно, наиболее важные, даже если лексические замыкания не используются, использование лексической области видимости изолирует программные модули от нежелательных взаимодействий. Из-за ограниченной видимости лексические переменные закрытые. Если один модуль A связывает лексическую переменную X и другой модуль B, ссылки на X в B не будут случайно разрешаться в X, привязанный к A. B просто не имеет доступа к X. В ситуациях, когда дисциплинированные взаимодействия через переменную являются желательно, Common Лисп специальный переменные. Специальные переменные позволяют модулю установить привязку для альтернативного X, которая видна другому модулю B, вызываемому из A. Возможность сделать это также является преимуществом; Следовательно, Common Lisp поддерживает как лексическую, так и динамическую область видимости.
A макрос в Лиспе внешне напоминает функцию в использовании. Однако вместо того, чтобы представить вычисляемое выражение, оно представляет преобразование исходного кода программы. Макрос получает источник в качестве аргументов, привязывает их к своим параметрам и вычисляет новую формулу. Эта новая форма также может использовать макрос. Расширение макроса повторяется до тех пор, пока новая исходная форма не перестанет использовать макрос. Окончательная расчетная форма - это исходный код, выполняемый во время выполнения.
Тип использования макросов в Лиспе:
Различные стандартные Common Lisp также должны быть реализованы в виде макросов, как:
setf
, позволяющая настраивать функции расширения во время компиляции операторов присваивания / доступа <таких 633>с-аксессуарами, с-слотами
, с-открытым файлом
и другими подобными С
макросамиif
или cond
является макросом, построенным на прочее, специальный оператор; , когда
и , если
не состоит из макросовцикл
предметно-зависимый языкМакросы макросом defmacro. Специальный макрос позволяет определять локальные макросы (с лексической областью видимости). Также можно определить макросы для символов с помощью define-symbol-macro и symbol-macrolet.
Книга Пола Грэма О Лиспе подробно использование макросов в Common Lisp. Книга расширяет обсуждение макросов, утверждая, что «макросы - это единственное самое большое преимущество, которое у Lisp как языка программирования, и самое большое преимущество любого языка программирования». Хойт приводит несколько примеров итеративной разработки макросов.
Макросы позволяют программистам на Лиспе создать новые синтаксические формы на языке. Типичное использование - создание новых структур управления. В примере использования конструкции цикла от до
. Синтаксис:
(до тестовой формы *)
Определение макроса для до:
(defmacro до (тест и тело тела) (let ((start-tag (gensym "START")) (end- tag ( gensym "КОНЕЦ"))) `(tagbody, start-tag (when, test (go, end-tag)) (progn, @ body) (go, start-tag), end-tag)))
tagbody - это примитивный специальный оператор Common Lisp, который дает возможность присваивать имена тегам и использовать форму перехода к этому тегам. Обратная кавычка `представляет собой нотацию, которая предоставляет шаблоны кода, где значения форм, предшествует запятая, заполняются. Формы, которым предшествует запятая и знак, соединяются. Форма tagbody проверяет конечное условие. Если условие истинно, выполнен переход к конечному тегу. В случае неисправного основного кода, выполняется переход к начальному тегу.
Пример использования указанного макроса до:
(до (= (random 10) 0) (строка записи «Hello»))
Код может быть расширен с помощью функций macroexpand- 1. Расширение для приведенного выше пример выглядит так:
(TAGBODY #: START1136 (WHEN (ZEROP (RANDOM 10)) (GO #: END1137)) (PROGN ( WRITE-LINE "привет")) (GO #: START1136) #: END1137)
Во время раскрытия макроса значение теста равно (= (random 10) 0), значение тела переменной - ((строка записи "Hello")). Тело - это список форм.
Символы обычно автоматически повышаются. Расширение использует TAGBODY с двумя метками. Символы для этих этикеток вычисляются GENSYM и не интернированы ни в какой пакет. Две формы идут использовать эти теги для перехода. Tagbody - это примитивный оператор в Common Lisp (а не макрос), он не будет расширен во что-то еще. В развернутой форме используется макрос, когда, который также будет внутри. Полное раскрытие исходной формы называется кодом кода.
В полностью развернутой (пройденной) форме форма, когда заменяется примитивом, если:
(TAGBODY #: START1136 (IF (ZEROP (RANDOM 10)) (PROGN (GO #: END1137)) NIL) (PROGN ( НАПИШИТЕ «привет»)) (GO #: START1136)) #: END1137)
Все макросы должны быть раскрыты, прежде чем исходный код, обеспечить их, можно будет оценить или скомпилировать в обычном режиме. Макросы можно рассматривать как функции, которые принимают и возвращают S-выражения - аналогично абстрактным синтаксическим деревьям, но не ограничиваются ими. Эти функции вызываются перед оценщиком или компилятором для создания окончательного исходного кода. Макросы написаны на обычном Common Lisp и могут использовать любой доступный Common Lisp (или сторонний) оператор.
Макросы Common Lisp способны к так называемому использованию чисел, когда символы в теле расширения макроса совпадают с разными символами в вызывающем контексте, что позволяет программисту создать макросы, в которых используются символы особое значение. Термин «блокирование» несколько вводит в заблуждение, потому что все пространства имен уязвимы для нежелательного пространства захвата, включая имен оператора и функции, пространство метки tagbody, тег catch, обработчик условий и имен пространства перезапуска.
Захват может привести к дефектам программного обеспечения. Это происходит одним из следующих способов:
Схема диалект системы в Lisp обеспечивает запись макросов, которая обеспечивает ссылочную прозрачность, исключающую оба типа захвата. проблема. Этот тип макросистемы иногда называют «гигиеничным», в частности, его сторонники (которые автоматически не решают эту проблему, негигиеничными).
В Common Lisp макросистема обеспечивается одним из двух различных путей.
Один из подходов заключается в использовании: гарантированно уникальных символов, которые находятся в макрорасширении без угрозы захвата. Использование генсимов в макросе - это ручная работа, но можно написать макросы, которые упростят создание и использование генсимов. Генсимы легко решают захват типа 2, но они не применимы к захвату типа 1 таким же образом, потому что расширение макроса не может переименовать мешающие символы в окружающем коде, которые захватывают его ссылки. Gensyms можно использовать для стабильных псевдонимов для глобальных символов, которые необходимы для макрорасширения. Расширение макроса будет использовать эти секретные псевдонимы, а не хорошо известные имена, поэтому переопределение хорошо известных имен не использует вредного воздействия на макрос.
Другой подход - использовать пакеты. Макрос, специфика в собственном пакете, может просто использовать внутренние символы этого пакета в своем расширении. Использование пакетов касается захвата типа 1 и типа 2.
Однако пакеты не решают проблему захвата типа 1 ссылок на стандартные функции и операторы Common Lisp. Причина в том, что использование пакетов для решения проблем захвата вращается вокруг обычных частных символов (символов в одном пакете, которые не становятся видимыми в других пакетах). В то время как символы библиотеки Common Lisp являются внешними и часто импортируются или становятся видимыми в пользовательских пакетах.
Ниже представлен пример рекомендательного захвата в имен операторов, происходящего при раскрытии макроса:
;; расширение UNTIL позволяет свободно использовать DO (defmacro until (expression body body) `(do () (, expression), @ body)) ;; macrolet устанавливает привязку лексического оператора для DO (macrolet ((do (...)... something else...)) (until (= (random 10) 0) (write-line "Hello")))
Макрос от до
раскроется в формулу, которая вызывает do
, которая предназначена для ссылок на стандартный макрос Common Lisp do
. Однако в этом контексте do
может иметь совершенно другое значение, поэтому до
может работать неправильно.
Common Lisp решает проблему дублирования стандартных операторов и функций, запрещая их переопределение. Он может быть переопределяет стандартный оператор do
, предыдущий фактически является фрагментом несоответствующего Common Lisp, что позволяет реализовать диагностировать и отклонить его.
Система условий отвечает за обработка исключений в Common Lisp. Он предоставляет условия, обработчики и перезапуски. Условия - это объекты, описывающие исключительную ситуацию (например, ошибку). Если указано условие, система Common Lisp ищет обработчик для этого типа условия и вызывает обработчик. Теперь обработчик может искать перезапуски и использовать один из этих перезапусков для устранения текущей проблемы, используя такую информацию, как типовые условия и любую соответствующую информацию, предоставленную как часть условий объекта, и вызвать соответствующую функцию перезапуска.
Эти перезапуски, если они не обрабатываются кодом, могут быть представлены пользователям (как часть пользовательского интерфейса, например, отладчика), чтобы пользователь мог выбрать и вызвать один из доступных перезапусков. Обработчик условий вызывается в контексте (без раскрутки стека), полное восстановление после ошибки во многих случаях, когда другие системы обработки исключений уже завершили бы текущую систему. Сам отладчик также можно настроить или заменить с помощью динамической модели * крючок-отладчик *
. Код, найденный в формах защиты от размотки, таких как финализаторы, также будет надлежащим образом, несмотря на исключение.
В следующем примере (с использованием Symbolics Genera ) пользователь пытается открыть файл в функциональном тесте Lisp, вызываемый из Read-Eval-Print-LOOP (REPL ), когда файл не существует. Система Lisp предлагает четыре перезапуска. Пользователь выбирает Retry OPEN, используя перезапуск с другим именем пути, и вводит другое имя пути (lispm-init.lisp вместо lispm-int.lisp). Код пользователя не содержит кода обработки ошибок. Весь код обработки ошибок и перезапуска обеспечивается системой Lisp, которая может обрабатывать и исправлять ошибку, не прерывая пользовательский код.
Команда: (test ">zippy>lispm-int.lisp") Ошибка: файл не найден. Для lispm:>zippy>lispm-int.lisp.newest LMFS: OPEN-LOCAL-LMFS-1 Arg 0: #P "lispm:>zippy>lispm-int.lisp.newest" sA,: повторить попытку ОТКРЫТЬ из lispm:>zippy>lispm-int.lisp.newest sB: повторить попытку OPEN, используя другой путь sC, : вернуться на верхний Lisp на сервере TELNET sD: перезапустить процесс Терминал TELNET ->повторить попытку OPEN, используя другое имя пути Использовать какой путь вместо этого [по умолчанию lispm:>zippy>lispm-int.lisp.newest]: lispm:>zippy>lispm-init.lisp.newest... программа продолжается
Common Lisp включает набор инструментов для объектно-ориентированного программирования, объектной системы Common Lisp или CLOS, которая является одной из самых мощных объектных систем, доступных на любом языке. Например, Питер Норвиг объясняет, сколько шаблонов проектирования Проще реализовать на своем языке функции CLOS (динамическое наследование, миксины, мультиметоды, метаклассы, комбинации методов и т. Д.). Было предложено включить несколько расширений Common Lisp для объектно-ориентированного программирования в стандартном ANSI Common Lisp, но в итоге CLOS была принята как стандартная объектная система для Common Lisp. CLOS - это динамическая объектная система с множественной отправкой и множественным наследованием, которая радикально отличается от возможностей ООП, других в статических языках, таких как C + + или Java. Как динамическая объектная система, CLOS позволяет улучшить во время выполнения общие функции и классы. Можно добавить методы, можно изменить классы, можно обновлять объекты для изменений класса и можно изменить класс объектов.
CLOS интегрирован в ANSI Common Lisp. Общие функции как Мои функции и другие функции первоклассный тип данных. Каждый класс CLOS интегрирован в систему типов Common Lisp. Многие типы Common Lisp имеют соответствующий класс. Для Common Lisp существует больше возможностей использовать CLOS. В спецификации не говорится, реализуются ли условия с помощью CLOS. Пути и потоки могут быть реализованы с помощью CLOS. Эти дополнительные возможности использования CLOS для ANSI Common Lisp не являются стандартом. Фактические реализации Common Lisp используют CLOS для имен путей, потоков, ввода-вывода, условий, самого реализации CLOS и многого другого.
Интерпретатор без ограничений исходный код Лиспа, предоставленные объекты Лиспа (списки, символы, числа,...), считанные из s-выражений. Компилятор Lisp генерирует байт-код или машинный код из исходного кода Lisp. Common Lisp позволяет компилировать как отдельные функции Lisp в память, так и компилировать целые файлы во внешне сохраненный скомпилированный код (файлы fasl).
Несколько реализаций ранних диалектов Лиспа, предоставляющих как интерпретатор, так и компилятор. К сожалению, часто семантика была иной. Эти более ранние Лиспы реализовали лексическую область видимости в компиляторе и динамическую область видимости в интерпретаторе. Common Lisp требует, чтобы и интерпретатор по умолчанию использовал лексическую область видимости. Стандарт Common Lisp присутствует семантику интерпретатора и компилятора. Компилятор может быть вызван использованием функций компиляции для отдельных функций и с использованием компиляции функций для файлов. Common Lisp позволяет типам объявления и способам влиять на политику генерации компилятора. Для последних различных качеств оптимизации могут быть заданы значения от 0 (не важно) до 3 (наиболее важно): скорость, пространство, безопасность, отладка и скорость компиляции.
Также существует функция для оценки кода Lisp: eval
. eval
принимает код как проанализированные s-выражения, а не, как на некоторых других языках, как текстовые строки. Таким образом, код может быть построен с помощью обычных функций Лиспа для построения списков и символов, а затем этот код может быть оценен с помощью функций eval
. Некоторые реализации Common Lisp (например, Clozure CL и SBCL) реализуют eval
с помощью своего компилятора. Таким образом код компилируется, даже если он оценивается с помощью функции eval
.
. Компилятор файла вызывается с помощью функции compile-file. Сгенерированный файл со скомпилированным кодом называется fasl (из быстрой быстрой). Эти файлы fasl, а также файлы исходного кода могут быть загружены с функцией загрузки работающей системы Common Lisp. В от реализации компилятора файла генерирует байт-код (например, для данной машины Java ), код языка C (который компилируется компилятором C) или напрямую, собственный код.
Реализации Common Lisp Program в интерактивном режиме, даже если код полностью компилируется. Таким образом, идея интерпретируемого языка неприменима к интерактивному Common Lisp.
Язык различает время чтения, время компиляции, время загрузки и время выполнения, и позволяет пользовательскому коду также делать это различие для выполнения желаемого типа обработки на желаемом этапе.
Некоторые специальные операторы специально предназначены для интерактивной разработки; например, defvar
присваивает значение предоставленной стандартной только в том случае, если она еще не привязана, тогда как defparameter
всегда будет выполнять присвоение. Это полезно при интерактивной оценке, компиляции и загрузке кода в живом изображении.
Некоторые функции также выплаты для помощи в написании компиляторов и интерпретаторов. Символы состоят из объектов первого уровня и напрямую управляются пользовательским кодом. Специальный оператор progv
позволяет программно создавать лексические привязки, при этом пакетами также можно управлять. Компилятор Lisp доступен во время выполнения для компиляции файлов или отдельных функций. Это упрощает использование Lisp в качестве промежуточного компилятора или интерпретатора для другого языка.
Следующая программа вычисляет наименьшее количество людей в комнате, для которых вероятность уникальных дней рождения составляет менее 50% (парадокс дня рождения, где для 1 человека вероятность очевидно 100%, для 2 - 364/365 и т. д.). Ответ: 23.
По соглашению, константы в Common Lisp заключаются в символы +.
(defconstant + year-size + 365) (defun birthday-paradox (вероятность-число-людей) (let ((новая-вероятность (* (/ (- + размер года + количество людей) + год- size +) вероятность))) (if (< new-probability 0.5) (1+ number-of-people) (birthday-paradox new-probability (1+ number-of-people)))))
Вызов функции-примера с использованием REPL (Read Eval Print Loop):
CL-USER>(birthday-paradox 1.0 1) 23
Мы определяем класс person
и метод для отображения имени и возраста человека. Затем мы определяем группу лиц как список объекты person
. Затем мы перебираем отсортированный список.
(defclass person () ((name: initarg: name: accessor person-name) (age: initarg: age: accessor person-age)) (: документация «Класс PERSON со слотами NAME и AGE.»)) (defmethod display ((object person) stream) «Отображение объекта PERSON в потоке вывода.» (with-slots (name age) object (format stream) ~ a (~ a) "имя возраст))) (defparameter * group * (list (make-instance 'person: name" Bob ": age 33) (make-instance' person: name" Chr is " : age 16) (make-instance 'person: name "Ash": age 23)) "Список объектов PERSON.") (dolist (person (sort (copy-list * group *) #'>: key # ' person-age)) (display person * standard-output *) (terpri))
Он печатает три имени в порядке убывания возраста.
Боб (33) Эш (23) Крис (16)
Демонстрация использования макроса LOOP:
(defun power (xn) (цикл с результатом = 1 while (plusp n) when (oddp n) do (setf result (* result x)) do (setf x (* xx) n (truncate n 2)) finally (return result)))
Пример использования:
CL-USER>(мощность 2 200) 1606938044258990275541962092341162602522202993782792835301376
Сравните со встроенным возведением в степень:
CL-USER>(= (expt 2 200) (power 2 200)) T
WITH-OPEN-FILE - это макрос, который открывает файл и предоставляет поток. Когда форма возвращается, файл автоматически закрывается. FUNCALL вызывает объект функции. LOOP собирает все строки, соответствующие предикату.
(defun list-matching-lines (предикат файла) «Возвращает список строк в файле, для которых примененный к строке предикат возвращает T.» (with-open-file (файл потока) (цикл для строки = (read-line stream nil nil) while line when (funcall predicate line) собирает его)))
Функция AVAILABLE-SHELLS вызывает вышеупомянутую функцию LIST-MATCHING-LINES с именем пути и анонимной функцией в качестве предиката. Предикат возвращает путь к оболочке или NIL (если строка не является именем файла оболочки).
(defun available-shells (optional (file #p "/ etc / shells")) (файл списка совпадающих строк (lambda (line) (and (plusp (length line)) (char = (char line 0) # \ /) (pathname (string-right-trim '(# \ space # \ tab) line))))))
Пример результатов (в Mac OS X 10.6):
CL-USER>( доступные оболочки) (#P "/ bin / bash" #P / bin / csh "#P" / bin / ksh "#P" / bin / sh "#P" / bin / tcsh "#P" / bin / zsh ")
Common Lisp наиболее часто сравнивается и противопоставляется Scheme - хотя бы потому, что это два самых популярных диалекта Lisp. Scheme предшествует CL и исходит не только из той же традиции Lisp, но и от некоторых из тех же инженеров - Гая Л. Стила, с которым Джеральд Джей Сассман разработал Scheme, председатель комитета по стандартам для Common Lisp.
Common Lisp - это язык программирования общего назначения, в отличие от вариантов Lisp, таких как Emacs Lisp и AutoLISP, которы е являются встроенными языками расширения в конкретных продуктах (GNU Emacs и AutoCAD соответственно). В отличие от многих более ранних Лиспов, Common Lisp (например, Scheme ) по умолчанию использует лексическую переменную scope как для интерпретируемого, так и для скомпилированного кода.
Большинство систем Лиспа, чьи конструкции способствовали развитию Common Lisp, такие как ZetaLisp и Franz Lisp, использовали переменные с динамической областью действия в своих интерпретаторах и переменные с лексической областью видимости в своих интерпретаторах. компиляторы. Scheme ввел в Lisp единственное использование переменных с лексической областью видимости; вдохновение из АЛГОЛА 68. CL также поддерживает переменные с динамической областью видимости, но они должны быть явно объявлены как «специальные». Нет различий в области видимости между интерпретаторами и компиляторами ANSI CL.
Common Lisp иногда называют Lisp-2, а Scheme - Lisp-1, имея в виду использование CL отдельных пространств имен для функций и переменных. (Фактически, CL имеет много пространств имен, например, для тегов go, имен блоков и ключевых слов loop
). Между сторонниками CL и Scheme существует давняя полемика по поводу компромиссов, связанных с несколькими пространствами имен. В Scheme (в широком смысле) необходимо избегать указания имен переменных, которые конфликтуют с функциями; Функции схемы часто имеют аргументы с именами lis
, lst
или lyst
, чтобы не конфликтовать с системной функцией list
. Однако в CL необходимо явно ссылаться на пространство имен функции при передаче функции в качестве аргумента - что также является обычным явлением, как в примере sort
выше.
CL также отличается от Scheme обработкой логических значений. Схема использует специальные значения #t и #f для представления истины и лжи. CL следует старому соглашению Lisp об использовании символов T и NIL, причем NIL также обозначает пустой список. В CL любое значение, отличное от NIL, трактуется как истинное условными операторами, такими как if
, тогда как в схеме все значения, отличные от # f, обрабатываются как истинные. Эти соглашения позволяют некоторым операторам на обоих языках служить как предикатами (отвечая на вопрос с логическим значением), так и возвращать полезное значение для дальнейшихвычислений, но в Scheme значение '(), которое эквивалентно NIL в Common Lisp, оценивается как истинное. в логическом выражении.
И, наконец, документы стандартов Схема требует оптимизации хвостового вызова, чего не требует стандарт CL. Большинство реализаций CL выполняет оптимизацию хвостового вызова, когда программист использует директиву оптимизации. Тем не менее, общий стиль кодирования CL не способствует повсеместному использованию рекурсии, которую предпочитает Схема - то, что программист Схема выразил бы хвостовой рекурсией, пользователь CL обычно выражает итеративным выражением в do
, dolist
, loop
или (совсем недавно) с пакетом итерация
.
См. Категорию Реализации Common Lisp.
Common Lisp определяется спецификацией (например, Ada и C ) без одной реализации (например, Perl ). Существуют различные реализаций и стандартные детали, в которых они действительно различаться.
Кроме того, реализация, как правило, поставляется с расширениями, которые обеспечивают функциональность, не охваченную стандартом:
Бесплатное программное обеспечение с открытым исходным кодом библиотеки были созданы для поддержки расширений Common Lisp переносимым способом, и в основном они находятся в ре позиториях Common-Lisp.net и CLOCC (Common Lisp Open Code Collection) проекты.
Реализации Common Lisp могут использовать любое сочетание компиляции собственного кода, компиляции или интерпретации байтового кода. Common Lisp разработан для поддержки инкрементных компиляторов, компиляторов файлов и блочных компиляторов. Стандартные объявления для оптимизации компиляции (например, встраивание функций или специализация типов) Указ в спецификации языка. Большинство реализаций Common Lisp компилируют исходный код в машинный код . Некоторые реализации могут создать (оптимизированные) автономные приложения. Другие компилируют в интерпретируемый байт-код , который менее эффективен, чем собственный код, но облегчает перенос двоичного кода. Некоторые компиляторы компилируют код Common Lisp в код C. Ошибочное представление о том, что Lisp является чисто интерпретируемым языком, скорее всего, связано с тем, что среда Lisp предоставляет интерактивные подсказки и этот код компилируется один за другим, поэтапно. В Common Lisp широко используется инкрементная компиляция.
Некоторые реализации на основе Unix (CLISP, SBCL ), как язык сценариев ; то есть вызывается системой прозрачно, как интерпретатор Perl или оболочки Unix.
Common Lisp для разработки исследовательских приложений, для быстрой разработки прототипов или для развернутых приложений.
Common Lisp используется во многих коммерческих приложениях, включая сайт веб-коммерции Yahoo! Магазин, в котором изначально участвовал Пол Грэм, а позже был переписан на C ++ и Perl.. Другие примечательные примеры включают:
Также существуют открытые - исходные приложения, написанные на Common Lisp, такие как:
Хронологический список опубликованных (или готовящихся к публикации) книг о Common Lisp (языке) или о программировании на Common Lisp (особенно программировании AI).
Викиучебники содержат больше информации по теме: Common Lisp |