Common Lisp ( CL ) — диалект языка программирования Lisp , опубликованный в стандартном документе Американского национального института стандартов (ANSI) ANSI INCITS 226-1994 (S2018) [1] (ранее X3.226-1994 (R1999) ). [2] Common Lisp HyperSpec , гиперссылочная версия HTML , была получена из стандарта ANSI Common Lisp. [3]
Язык Common Lisp был разработан как стандартизированный и улучшенный преемник Maclisp . К началу 1980-х годов несколько групп уже работали над различными преемниками MacLisp: Lisp Machine Lisp (он же ZetaLisp), Spice Lisp , NIL и S-1 Lisp . Common Lisp стремился унифицировать, стандартизировать и расширить возможности этих диалектов MacLisp. Common Lisp — это не реализация, а скорее языковая спецификация . [4] Доступно несколько реализаций стандарта Common Lisp, включая бесплатное и открытое программное обеспечение и фирменные продукты. [5] Common Lisp — это универсальный многопарадигменный язык программирования . Он поддерживает комбинацию процедурных , функциональных и объектно-ориентированных парадигм программирования. Как динамический язык программирования , он облегчает эволюционную и инкрементальную разработку программного обеспечения с итеративной компиляцией в эффективные исполняемые программы. Эта инкрементальная разработка часто выполняется интерактивно, не прерывая работающее приложение.
Он также поддерживает необязательную аннотацию типа и приведение, которые могут быть добавлены по мере необходимости на более поздних этапах профилирования и оптимизации, чтобы позволить компилятору генерировать более эффективный код. Например, fixnum
может содержать неупакованное целое число в диапазоне, поддерживаемом оборудованием и реализацией, что позволяет выполнять более эффективную арифметику, чем на больших целых числах или типах произвольной точности. Аналогично, компилятору можно указать на основе модуля или функции, какой тип уровня безопасности требуется, используя декларацию optimize .
Common Lisp включает CLOS — объектную систему , которая поддерживает мультиметоды и комбинации методов. Часто реализуется с помощью протокола метаобъектов .
Common Lisp расширяем с помощью стандартных функций, таких как макросы Lisp (преобразования кода) и макросы чтения (анализаторы входных данных для символов).
Common Lisp обеспечивает частичную обратную совместимость с Maclisp и оригинальным Lisp Джона Маккарти . Это позволяет портировать старое программное обеспечение Lisp на Common Lisp. [6]
Работа над Common Lisp началась в 1981 году по инициативе менеджера ARPA Боба Энгельмора по разработке единого стандартного диалекта Lisp для сообщества. [7] Большая часть первоначального проектирования языка была выполнена по электронной почте. [8] [9] В 1982 году Гай Л. Стил-младший дал первый обзор Common Lisp на симпозиуме ACM 1982 года по LISP и функциональному программированию. [10]
Первая документация языка была опубликована в 1984 году как Common Lisp the Language (известный как CLtL1), первое издание. Второе издание (известное как CLtL2), опубликованное в 1990 году, включало множество изменений в язык, внесенных в процессе стандартизации ANSI Common Lisp: расширенный синтаксис LOOP, Common Lisp Object System, Condition System для обработки ошибок, интерфейс к красивому принтеру и многое другое. Но CLtL2 не описывает окончательный стандарт ANSI Common Lisp и, таким образом, не является документацией ANSI Common Lisp. Окончательный стандарт ANSI Common Lisp был опубликован в 1994 году. С тех пор никаких обновлений стандарта не публиковалось. Различные расширения и улучшения Common Lisp (примерами являются Unicode, Concurrency, CLOS-based IO) были предоставлены реализациями и библиотеками .
Common Lisp — это диалект Lisp. Он использует S-выражения для обозначения как кода, так и структуры данных. Вызовы функций, макроформы и специальные формы записываются в виде списков, с именем оператора на первом месте, как в этих примерах:
( + 2 2 ) ; складывает 2 и 2, давая 4. Имя функции — '+'. В Lisp нет операторов как таковых.
( defvar *x* ) ; Гарантирует, что переменная *x* существует, ; не присваивая ей значения. Звездочки являются частью ; имени, по соглашению обозначая специальную (глобальную) переменную. ; Символ *x* также настоящим наделяется свойством, что ; последующие его привязки являются динамическими, а не лексическими. ( setf *x* 42.1 ) ; Устанавливает переменную *x* в значение с плавающей точкой 42.1
;; Определите функцию, которая возводит число в квадрат: ( defun square ( x ) ( * x x ))
;; Выполнить функцию: ( квадрат 3 ) ; Возвращает 9
;; Конструкция 'let' создает область действия для локальных переменных. Здесь ;; переменная 'a' привязана к 6, а переменная 'b' привязана к ;; 4. Внутри 'let' находится 'body', в котором возвращается последнее вычисленное значение. ;; Здесь возвращается результат сложения a и b из выражения 'let'. ;; Переменные a и b имеют лексическую область действия, если только символы не были ;; помечены как специальные переменные (например, предшествующим DEFVAR). ( let (( a 6 ) ( b 4 )) ( + a b )) ; возвращает 10
В Common Lisp имеется множество типов данных .
Числовые типы включают целые числа , отношения , числа с плавающей точкой и комплексные числа . [11] Common Lisp использует bignums для представления числовых значений произвольного размера и точности. Тип отношения представляет дроби точно, что недоступно во многих языках. Common Lisp автоматически приводит числовые значения между этими типами по мере необходимости.
Тип символов Common Lisp не ограничивается символами ASCII . Большинство современных реализаций допускают символы Unicode . [12]
Тип символа является общим для языков Lisp, но в значительной степени неизвестен за их пределами. Символ — это уникальный именованный объект данных, состоящий из нескольких частей: имя, значение, функция, список свойств и пакет. Из них наиболее важны ячейка значения и ячейка функции . Символы в Lisp часто используются аналогично идентификаторам в других языках: для хранения значения переменной; однако есть и много других применений. Обычно, когда символ оценивается, его значение возвращается. Некоторые символы оцениваются сами по себе, например, все символы в ключевом слове package являются самооценивающимися. Булевы значения в 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 состоят из cons , иногда называемых cons-ячейками или парами . Cons — это структура данных с двумя слотами, называемыми car и cdr . Список — это связанная цепочка cons или пустой список. Car каждого cons ссылается на члена списка (возможно, на другой список). Cdr каждого cons ссылается на следующий cons — за исключением последнего cons в списке, cdr которого ссылается на nil
значение. Cons также можно легко использовать для реализации деревьев и других сложных структур данных; хотя обычно рекомендуется вместо этого использовать экземпляры структур или классов. Также можно создавать циклические структуры данных с помощью cons.
Common Lisp поддерживает многомерные массивы и может динамически изменять размер регулируемых массивов, если это необходимо. Многомерные массивы могут использоваться для матричной математики. Вектор — это одномерный массив. Массивы могут содержать любой тип в качестве членов (даже смешанные типы в одном массиве) или могут быть специализированы для содержания определенного типа членов, как в векторе битов. Обычно поддерживаются только несколько типов. Многие реализации могут оптимизировать функции массивов, когда используемый массив является специализированным по типу. Стандартными являются два специализированных по типу типа массива: строка — это вектор символов, а битовый вектор — это вектор битов .
Хеш-таблицы хранят ассоциации между объектами данных. Любой объект может быть использован как ключ или значение. Хеш-таблицы автоматически изменяют размер по мере необходимости.
Пакеты — это наборы символов, используемые в основном для разделения частей программы на пространства имен . Пакет может экспортировать некоторые символы, отмечая их как часть публичного интерфейса. Пакеты могут использовать другие пакеты.
Структуры , аналогичные по использованию структурам C и записям Pascal , представляют собой произвольные сложные структуры данных с любым количеством и типом полей (называемых слотами ). Структуры допускают одиночное наследование.
Классы похожи на структуры, но предлагают больше динамических возможностей и множественное наследование. (См. CLOS ). Классы были добавлены в Common Lisp поздно, и существует некоторое концептуальное совпадение со структурами. Объекты, созданные из классов, называются экземплярами . Особый случай — универсальные функции. Универсальные функции являются как функциями, так и экземплярами.
Common Lisp поддерживает функции первого класса . Например, можно писать функции, которые принимают другие функции в качестве аргументов или также возвращают функции. Это позволяет описывать очень общие операции.
Библиотека Common Lisp в значительной степени опирается на такие функции высшего порядка. Например, sort
функция принимает реляционный оператор в качестве аргумента и ключевую функцию в качестве необязательного ключевого аргумента. Это можно использовать не только для сортировки любого типа данных, но и для сортировки структур данных по ключу.
;; Сортирует список, используя функции > и < в качестве оператора отношения. ( sort ( list 5 2 6 3 1 4 ) #' > ) ; Возвращает (6 5 4 3 2 1) ( sort ( list 5 2 6 3 1 4 ) #' < ) ; Возвращает (1 2 3 4 5 6)
;; Сортирует список по первому элементу каждого подсписка. ( sort ( list ' ( 9 A ) ' ( 3 B ) ' ( 4 C )) #' < :key #' first ) ; Возвращает ((3 B) (4 C) (9 A))
Модель оценки для функций очень проста. Когда оценщик сталкивается с формой (f a1 a2...)
, он предполагает, что символ с именем f является одним из следующих:
lambda
.Если f — имя функции, то аргументы a1, a2, ..., an вычисляются слева направо, а функция находится и вызывается с этими значениями, указанными в качестве параметров.
Макрос определяет функции ,defun
где определение функции содержит имя функции, имена всех аргументов и тело функции:
( defun квадрат ( x ) ( * x x ))
Определения функций могут включать директивы компилятора , известные как декларации , которые предоставляют подсказки компилятору о настройках оптимизации или типах данных аргументов. Они также могут включать строки документации (docstrings), которые система Lisp может использовать для предоставления интерактивной документации:
( defun square ( x ) "Вычисляет квадрат числа с плавающей точкой x." ( declare ( single-float x ) ( optimize ( speed 3 ) ( debug 0 ) ( safety 1 ))) ( the single-float ( * x x )))
Анонимные функции ( литералы функций ) определяются с помощью lambda
выражений, например, (lambda (x) (* x x))
для функции, которая возводит свой аргумент в квадрат. Стиль программирования на Lisp часто использует функции высшего порядка, для которых полезно предоставлять анонимные функции в качестве аргументов.
Локальные функции можно определить с помощью flet
и labels
.
( флет (( квадрат ( x ) ( * x x ))) ( квадрат 3 ))
Есть несколько других операторов, связанных с определением и манипулированием функциями. Например, функция может быть скомпилирована с помощью compile
оператора. (Некоторые системы Lisp запускают функции с использованием интерпретатора по умолчанию, если не указано компилировать; другие компилируют каждую функцию).
Макрос defgeneric
определяет общие функции . Общие функции представляют собой набор методов . Макрос defmethod
определяет методы.
Методы могут специализировать свои параметры по стандартным классам CLOS , системным классам , структурным классам или отдельным объектам. Для многих типов существуют соответствующие системные классы .
При вызове универсальной функции множественная диспетчеризация определит эффективный метод для использования.
( defgeneric add ( a b ))
( defmethod add (( число ) ( число b ) ) ( + a b ))
( defmethod add (( вектор ) ( число b )) ( map 'vector ( лямбда ( n ) ( + n b ) ) a ))
( defmethod add (( a vector ) ( b vector )) ( map 'vector #' + a b ))
( defmethod add (( a string ) ( b string )) ( concatenate 'string a b ))
( добавить 2 3 ) ; возвращает 5 ( добавить #( 1 2 3 4 ) 7 ) ; возвращает #(8 9 10 11) ( добавить #( 1 2 3 4 ) #( 4 3 2 1 )) ; возвращает #(5 5 5 5) ( добавить "COMMON " "LISP" ) ; возвращает "COMMON LISP"
Универсальные функции также являются типом данных первого класса . Универсальные функции и методы обладают гораздо большим количеством функций, чем описано выше.
Пространство имен для имен функций отделено от пространства имен для переменных данных. Это ключевое различие между Common Lisp и Scheme . Для Common Lisp операторы, определяющие имена в пространстве имен функций, включают defun
, flet
, labels
, defmethod
и defgeneric
.
Чтобы передать функцию по имени в качестве аргумента другой функции, необходимо использовать function
специальный оператор, обычно сокращенно обозначаемый как #'
. Первый sort
пример выше относится к функции, названной символом >
в пространстве имен функций, с кодом #'>
. И наоборот, чтобы вызвать функцию, переданную таким образом, следует использовать funcall
оператор для аргумента.
Модель оценки Scheme проще: есть только одно пространство имен, и все позиции в форме оцениваются (в любом порядке) – а не только аргументы. Поэтому код, написанный на одном диалекте, иногда сбивает с толку программистов, более опытных в другом. Например, многие программисты Common Lisp любят использовать описательные имена переменных, такие как список или строка , которые могут вызывать проблемы в Scheme, поскольку они локально затеняют имена функций.
Является ли отдельное пространство имен для функций преимуществом — это источник разногласий в сообществе Lisp. Обычно это называют дебатами Lisp-1 и Lisp-2 . Lisp-1 относится к модели Scheme, а Lisp-2 — к модели Common Lisp. Эти названия были придуманы в статье 1988 года Ричарда П. Габриэля и Кента Питмана , в которой подробно сравниваются два подхода. [13]
Common Lisp поддерживает концепцию множественных значений [14] , где любое выражение всегда имеет одно первичное значение , но оно также может иметь любое количество вторичных значений , которые могут быть получены и проверены заинтересованными вызывающими. Эта концепция отличается от возврата значения списка, поскольку вторичные значения полностью необязательны и передаются через выделенный сторонний канал. Это означает, что вызывающие могут оставаться совершенно не осведомленными о наличии вторичных значений, если они им не нужны, и это делает удобным использование механизма для передачи информации, которая иногда полезна, но не всегда необходима. Например,
TRUNCATE
евклидова деления :( пусть (( x 1266778 ) ( y 458 )) ( связывание-множественных-значений ( остаток от частного ) ( усечение x y ) ( формат nil "~A деленное на ~A равно ~A остаток ~A" x y остаток от частного ))) ;;;; => "1266778 делится на 458, получается 2765, остаток 408"
GETHASH
[16] возвращает значение ключа в ассоциативной карте или значение по умолчанию в противном случае и вторичное логическое значение, указывающее, было ли найдено значение. Таким образом, код, который не заботится о том, было ли найдено значение или предоставлено как значение по умолчанию, может просто использовать его как есть, но когда такое различие важно, он может проверить вторичное логическое значение и отреагировать соответствующим образом. Оба варианта использования поддерживаются одним и тем же вызовом, и ни один из них не обременен или не ограничен другим. Наличие этой функции на уровне языка устраняет необходимость проверять существование ключа или сравнивать его с null, как это было бы сделано в других языках.( defun get-answer ( библиотека ) ( gethash 'answer библиотека 42 )) ( defun the-answer-1 ( library ) ( format nil "Ответ ~A" ( get-answer library ))) ;;;; Возвращает "Ответ 42", если ОТВЕТ отсутствует в БИБЛИОТЕКЕ ( defun the-answer-2 ( library ) ( multiple-value-bind ( answer sure-p ) ( get-answer library ) ( if ( not sure-p ) "Я не знаю" ( format nil "Ответ ~A" answer )))) ;;;; Возвращает "Я не знаю", если ОТВЕТ отсутствует в БИБЛИОТЕКЕ
Множественные значения поддерживаются несколькими стандартными формами, наиболее распространенными из которых являются MULTIPLE-VALUE-BIND
специальная форма для доступа к вторичным значениям и VALUES
для возврата множественных значений:
( defun magic-eight-ball () "Вернуть прогноз прогноза, с вероятностью в качестве вторичного значения" ( values "Прогноз хороший" ( random 1.0 ))) ;;;; => "Перспективы хорошие" ;;;; => 0.3187
Другие типы данных в Common Lisp включают:
Как и программы на многих других языках программирования, программы Common Lisp используют имена для ссылки на переменные, функции и многие другие виды сущностей. Именованные ссылки подчиняются области действия.
Связь между именем и сущностью, на которую это имя ссылается, называется привязкой.
Область применения относится к набору обстоятельств, при которых определяется, что имя имеет определенную привязку.
Обстоятельства, определяющие область действия в Common Lisp, включают в себя:
(go x)
означает передачу управления метке x
, тогда как (print x)
относится к переменной x
. Обе области действия x
могут быть активны в одной и той же области текста программы, поскольку метки tagbody находятся в отдельном пространстве имен от имен переменных. Специальная форма или форма макроса полностью контролирует значения всех символов в своем синтаксисе. Например, в , (defclass x (a b) ())
определении класса, (a b)
является списком базовых классов, поэтому эти имена ищутся в пространстве имен классов и x
не является ссылкой на существующую привязку, а именем нового класса, полученного из a
и b
. Эти факты вытекают исключительно из семантики defclass
. Единственный общий факт об этом выражении заключается в том, что defclass
относится к привязке макроса; все остальное зависит от defclass
.x
заключена в конструкцию привязки, такую как a let, которая определяет привязку для x
, то ссылка находится в области действия, созданной этой привязкой.Чтобы понять, на что ссылается символ, программист Common Lisp должен знать, какой тип ссылки выражается, какой тип области действия она использует, если это переменная ссылка (динамическая или лексическая область действия), а также ситуацию во время выполнения: в какой среде разрешается ссылка, где в среду была введена привязка и т. д.
Некоторые среды в Lisp являются глобально распространенными. Например, если определен новый тип, он известен везде после этого. Ссылки на этот тип ищут его в этой глобальной среде.
Одним из типов окружения в Common Lisp является динамическое окружение. Привязки, установленные в этом окружении, имеют динамическую протяженность, что означает, что привязка устанавливается в начале выполнения некоторой конструкции, например let
блока, и исчезает, когда эта конструкция завершает выполнение: ее время жизни привязано к динамической активации и деактивации блока. Однако динамическая привязка не только видна внутри этого блока; она также видна всем функциям, вызываемым из этого блока. Этот тип видимости известен как неопределенная область действия. Привязки, которые демонстрируют динамическую протяженность (время жизни привязано к активации и деактивации блока) и неопределенную область действия (видимы всем функциям, вызываемым из этого блока), называются имеющими динамическую область действия.
Common Lisp поддерживает динамически ограниченные переменные, которые также называются специальными переменными. Некоторые другие виды привязок обязательно также имеют динамическую область видимости, например, перезапуски и теги catch. Связывания функций не могут иметь динамическую область видимости с помощью flet
(что обеспечивает только лексически ограниченные привязки функций), но объекты функций (объект первого уровня в Common Lisp) могут быть назначены динамически ограниченным переменным, связаны с помощью let
в динамической области видимости, а затем вызваны с помощью funcall
или APPLY
.
Динамическая область действия чрезвычайно полезна, поскольку она добавляет ссылочную ясность и дисциплину глобальным переменным . Глобальные переменные не одобряются в компьютерной науке как потенциальные источники ошибок, поскольку они могут привести к появлению специальных, скрытых каналов связи между модулями, которые приводят к нежелательным, неожиданным взаимодействиям.
В Common Lisp специальная переменная, имеющая только привязку верхнего уровня, ведет себя так же, как глобальная переменная в других языках программирования. В нее можно сохранить новое значение, и это значение просто заменит то, что находится в привязке верхнего уровня. Небрежная замена значения глобальной переменной лежит в основе ошибок, вызванных использованием глобальных переменных. Однако другой способ работы со специальной переменной — дать ей новую локальную привязку внутри выражения. Иногда это называют «перепривязкой» переменной. Привязка переменной с динамической областью действия временно создает новое место в памяти для этой переменной и связывает имя с этим местом. Пока эта привязка действует, все ссылки на эту переменную ссылаются на новую привязку; предыдущая привязка скрыта. Когда выполнение выражения привязки завершается, временное место в памяти исчезает, а старая привязка раскрывается, при этом исходное значение остается нетронутым. Конечно, несколько динамических привязок для одной и той же переменной могут быть вложенными.
В реализациях Common Lisp, поддерживающих многопоточность, динамические области видимости специфичны для каждого потока выполнения. Таким образом, специальные переменные служат абстракцией для локального хранилища потока. Если один поток повторно связывает специальную переменную, это повторное связывание не влияет на эту переменную в других потоках. Значение, сохраненное в привязке, может быть извлечено только потоком, создавшим эту привязку. Если каждый поток связывает некоторую специальную переменную *x*
, то *x*
ведет себя как локальное хранилище потока. Среди потоков, которые не повторно связывают *x*
, он ведет себя как обычный глобальный: все эти потоки ссылаются на одну и ту же привязку верхнего уровня *x*
.
Динамические переменные могут использоваться для расширения контекста выполнения с помощью дополнительной контекстной информации, которая неявно передается из функции в функцию без необходимости появляться в качестве дополнительного параметра функции. Это особенно полезно, когда передача управления должна проходить через слои не связанного кода, который просто не может быть расширен дополнительными параметрами для передачи дополнительных данных. Такая ситуация обычно требует глобальной переменной. Эта глобальная переменная должна быть сохранена и восстановлена, чтобы схема не сломалась при рекурсии: динамическое повторное связывание переменных заботится об этом. И эта переменная должна быть сделана локальной для потока (или должен быть использован большой мьютекс), чтобы схема не сломалась при потоках: реализации динамической области действия также могут заботиться об этом.
В библиотеке Common Lisp есть много стандартных специальных переменных. Например, все стандартные потоки ввода-вывода хранятся в привязках верхнего уровня известных специальных переменных. Стандартный поток вывода хранится в *standard-output*.
Предположим, что функция foo записывает в стандартный вывод:
( defun foo () ( format t "Привет, мир" ))
Чтобы захватить вывод в виде строки символов, *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 сначала оценивается форма setf, которая сохраняет функцию в специальной переменной *stashed*. Затем (go end-label) передает управление end-label, пропуская код (print "Hello"). Поскольку end-label находится в конце tagbody, tagbody завершается, возвращая NIL. Предположим, что теперь вызывается ранее запомненная функция:
( funcall *stashed* ) ;; Ошибка!
Эта ситуация ошибочна. Ответ одной реализации — это состояние ошибки, содержащее сообщение «GO: tagbody для тега SOME-LABEL уже оставлен». Функция попыталась оценить (go some-label), который лексически встроен в tagbody, и разрешается в label. Однако tagbody не выполняется (его экстент закончился), и поэтому передача управления не может быть выполнена.
Локальные привязки функций в Lisp имеют лексическую область действия , а привязки переменных также имеют лексическую область действия по умолчанию. В отличие от меток GO, обе они имеют неопределенную протяженность. Когда устанавливается лексическая функция или привязка переменной, эта привязка продолжает существовать до тех пор, пока возможны ссылки на нее, даже после того, как конструкция, установившая эту привязку, завершилась. Ссылки на лексические переменные и функции после завершения их устанавливающей конструкции возможны благодаря лексическим замыканиям .
Лексическое связывание является режимом связывания по умолчанию для переменных Common Lisp. Для отдельного символа его можно переключить в динамическую область видимости либо локальным объявлением, либо глобальным объявлением. Последнее может происходить неявно с помощью конструкции типа DEFVAR или DEFPARAMETER. В программировании Common Lisp важным соглашением является то, что специальные (т. е. динамически ограниченные) переменные имеют имена, которые начинаются и заканчиваются символом звездочки в *
том, что называется « соглашением earmuff ». [17] Если его придерживаться, это соглашение фактически создает отдельное пространство имен для специальных переменных, так что переменные, которые должны быть лексическими, случайно не становятся специальными.
Лексический охват полезен по нескольким причинам.
Во-первых, ссылки на переменные и функции могут быть скомпилированы в эффективный машинный код, поскольку структура среды выполнения относительно проста. Во многих случаях ее можно оптимизировать для стекового хранения, поэтому открытие и закрытие лексических областей имеет минимальные накладные расходы. Даже в случаях, когда необходимо сгенерировать полные замыкания, доступ к среде замыкания по-прежнему эффективен; обычно каждая переменная становится смещением в вектор привязок, и поэтому ссылка на переменную становится простой инструкцией загрузки или сохранения с режимом адресации «база плюс смещение» .
Во-вторых, лексическая область действия (в сочетании с неопределенной протяженностью) порождает лексическое замыкание , которое, в свою очередь, создает целую парадигму программирования, сосредоточенную вокруг использования функций как объектов первого класса, что лежит в основе функционального программирования.
В-третьих, возможно, самое важное, даже если лексические замыкания не используются, использование лексической области действия изолирует программные модули от нежелательных взаимодействий. Из-за их ограниченной видимости лексические переменные являются закрытыми. Если один модуль A связывает лексическую переменную X и вызывает другой модуль B, ссылки на X в B не будут случайно разрешаться в связанную X в A. B просто не имеет доступа к X. Для ситуаций, в которых желательны дисциплинированные взаимодействия через переменную, Common Lisp предоставляет специальные переменные. Специальные переменные позволяют модулю A устанавливать привязку для переменной X, которая видна другому модулю B, вызываемому из A. Возможность сделать это является преимуществом, и возможность предотвратить это также является преимуществом; следовательно, Common Lisp поддерживает как лексическую, так и динамическую область действия .
Макрос в Lisp внешне напоминает функцию в использовании. Однако, вместо того, чтобы представлять выражение, которое вычисляется, он представляет собой преобразование исходного кода программы. Макрос получает исходный код, который он окружает, в качестве аргументов, связывает их со своими параметрами и вычисляет новую исходную форму. Эта новая форма также может использовать макрос. Расширение макроса повторяется до тех пор, пока новая исходная форма не перестанет использовать макрос. Окончательная вычисленная форма — это исходный код, выполняемый во время выполнения.
Типичное использование макросов в Lisp:
Различные стандартные функции Common Lisp также необходимо реализовать в виде макросов, например:
setf
абстракция, позволяющая настраивать расширения операторов присваивания/доступа во время компиляцииwith-accessors
, with-slots
, with-open-file
и другие подобные WITH
макросыif
или cond
макрос построен на другом, специальный оператор; when
и unless
состоят из макросовloop
предметно-ориентированный языкМакросы определяются макросом defmacro . Специальный оператор macrolet позволяет определять локальные (лексически ограниченные) макросы. Также возможно определять макросы для символов с помощью define-symbol-macro и symbol-macrolet .
В книге Пола Грэма On Lisp подробно описывается использование макросов в Common Lisp. В книге Дуга Хойта Let Over Lambda обсуждение макросов расширяется, и он утверждает: «Макросы — это единственное величайшее преимущество LISP как языка программирования и единственное величайшее преимущество любого языка программирования». Хойт приводит несколько примеров итеративной разработки макросов.
Макросы позволяют программистам Lisp создавать новые синтаксические формы в языке. Одно из типичных применений — создание новых структур управления. Пример макроса предоставляет until
циклическую конструкцию. Синтаксис:
(до тестовой формы*)
Определение макроса для until :
( defmacro until ( test &body body ) ( let (( start-tag ( gensym "START" )) ( end-tag ( gensym "END" ))) ` ( tagbody , start-tag ( when , test ( go , end-tag )) ( progn ,@ body ) ( go , start-tag ) , end-tag )))
tagbody — это примитивный специальный оператор Common Lisp, который позволяет называть теги и использовать форму go для перехода к этим тегам. Обратная кавычка ` обеспечивает нотацию, которая предоставляет шаблоны кода, в которых заполняются значения форм, предваряемых запятой. Формы, предваряемые запятой и знаком at, вставляются . Форма tagbody проверяет конечное условие. Если условие истинно, выполняется переход к конечному тегу. В противном случае выполняется предоставленный код тела, а затем выполняется переход к начальному тегу.
Пример использования приведенного выше макроса until :
( до ( = ( случайное число 10 ) 0 ) ( write-line "Привет" ))
Код можно расширить с помощью функции macroexpand-1 . Расширение для приведенного выше примера выглядит следующим образом:
( ТЕГ #: START1136 ( КОГДА ( НОЛЬ ( СЛУЧАЙНЫЕ 10 )) ( GO #: END1137 )) ( PROGN ( СТРОКА ЗАПИСИ "привет" )) ( GO #: START1136 ) #: END1137 )
Во время макрорасширения значение переменной test равно (= (random 10) 0), а значение переменной body равно ((write-line "Hello")) . Тело представляет собой список форм.
Символы обычно автоматически записываются в верхнем регистре. Расширение использует TAGBODY с двумя метками. Символы для этих меток вычисляются GENSYM и не интернируются ни в один пакет. Две формы go используют эти теги для перехода. Поскольку tagbody является примитивным оператором в Common Lisp (а не макросом), он не будет расширен во что-то еще. Расширенная форма использует макрос when , который также будет расширен. Полное расширение исходной формы называется code walking .
В полностью развернутой ( walked ) форме форма when заменяется примитивной if :
( ТЕГ #: START1136 ( IF ( НОЛЬ ( СЛУЧАЙНЫЕ 10 )) ( ПРОГНОЗ ( GO #: END1137 )) НОЛЬ ) ( ПРОГНОЗ ( СТРОКА ЗАПИСИ "привет" )) ( GO #: START1136 )) #: END1137 )
Все макросы должны быть расширены, прежде чем исходный код, содержащий их, может быть оценен или скомпилирован обычным образом. Макросы можно считать функциями, которые принимают и возвращают S-выражения — аналогично абстрактным синтаксическим деревьям , но не ограничиваются ими. Эти функции вызываются до оценщика или компилятора для создания окончательного исходного кода. Макросы пишутся на обычном Common Lisp и могут использовать любой доступный оператор Common Lisp (или сторонний).
Макросы Common Lisp способны на то, что обычно называется захватом переменных , где символы в теле макрорасширения совпадают с символами в вызывающем контексте, что позволяет программисту создавать макросы, в которых различные символы имеют особое значение. Термин захват переменных несколько вводит в заблуждение, поскольку все пространства имен уязвимы для нежелательного захвата, включая пространство имен операторов и функций, пространство имен меток tagbody, тег catch, обработчик условий и пространства имен restart.
Переменный захват может привести к дефектам программного обеспечения. Это происходит одним из следующих двух способов:
Диалект Scheme языка Lisp предоставляет систему макрозаписи, которая обеспечивает ссылочную прозрачность, устраняющую оба типа проблемы захвата. Этот тип макросистемы иногда называют «гигиенической», в частности, ее сторонниками (которые считают макросистемы, которые автоматически не решают эту проблему, негигиеничными). [ необходима цитата ]
В Common Lisp гигиена макросов обеспечивается одним из двух способов.
Один из подходов заключается в использовании gensyms : гарантированно уникальные символы, которые могут использоваться в макрорасширении без угрозы захвата. Использование gensyms в определении макроса — это ручная работа, но можно написать макросы, которые упрощают создание и использование gensyms. Gensyms легко решают захват типа 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" )))
Макрос until
будет расширяться в форму, которая вызывает do
, которая предназначена для ссылки на стандартный макрос Common Lisp do
. Однако в этом контексте do
может иметь совершенно другое значение, поэтому until
может работать некорректно.
Common Lisp решает проблему затенения стандартных операторов и функций, запрещая их переопределение. Поскольку он переопределяет стандартный оператор do
, предыдущий фактически является фрагментом несоответствующего Common Lisp, что позволяет реализациям диагностировать и отклонять его.
Система условий отвечает за обработку исключений в Common Lisp. [18] Она предоставляет условия , обработчики и перезапуски . Условия — это объекты, описывающие исключительную ситуацию (например, ошибку). Если условие сигнализируется, система Common Lisp ищет обработчик для этого типа условия и вызывает обработчик. Теперь обработчик может искать перезапуски и использовать один из этих перезапусков для автоматического исправления текущей проблемы, используя такую информацию, как тип условия и любую соответствующую информацию, предоставленную как часть объекта условия, и вызывать соответствующую функцию перезапуска.
Эти перезапуски, если они не обрабатываются кодом, могут быть представлены пользователям (как часть пользовательского интерфейса, например, отладчика), так что пользователь может выбрать и вызвать один из доступных перезапусков. Поскольку обработчик условий вызывается в контексте ошибки (без раскручивания стека), во многих случаях возможно полное восстановление после ошибки, когда другие системы обработки исключений уже завершили бы текущую процедуру. Сам отладчик также может быть настроен или заменен с помощью *debugger-hook*
динамической переменной. Код, обнаруженный в формах unwind-protect, таких как финализаторы, также будет выполнен соответствующим образом, несмотря на исключение.
В следующем примере (с использованием 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.newestLMFS:ОТКРЫТЫЙ-ЛОКАЛЬНЫЙ-LMFS-1 Аргумент 0: #P"lispm:>zippy>lispm-int.lisp.newest"sA, <Resume>: Повторить попытку ОТКРЫТИЯ lispm:>zippy>lispm-int.lisp.newestsB: Повторите попытку OPEN, используя другой путьsC, <Abort>: Возврат на верхний уровень Lisp на сервере TELNETsD: Перезапустить процесс TELNET-терминала-> Повторите попытку OPEN, используя другой путьВместо этого используйте следующий путь [по умолчанию lispm:>zippy>lispm-int.lisp.newest]: lispm:>zippy>lispm-init.lisp.новейший...программа продолжается
Common Lisp включает в себя набор инструментов для объектно-ориентированного программирования , Common Lisp Object System или CLOS . Питер Норвиг объясняет, как многие шаблоны проектирования проще реализовать в динамическом языке с функциями CLOS (множественное наследование, миксины, мультиметоды, метаклассы, комбинации методов и т. д.). [19] Несколько расширений 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 предоставляли как интерпретатор, так и компилятор. К сожалению, часто семантика была разной. Эти более ранние Lisp реализовали лексическую область видимости в компиляторе и динамическую область видимости в интерпретаторе. Common Lisp требует, чтобы и интерпретатор, и компилятор использовали лексическую область видимости по умолчанию. Стандарт Common Lisp описывает как семантику интерпретатора, так и компилятора. Компилятор можно вызвать с помощью функции compile для отдельных функций и с помощью функции compile-file для файлов. Common Lisp допускает объявления типов и предоставляет способы влияния на политику генерации кода компилятора. Для последнего различным качествам оптимизации можно задать значения от 0 (не важно) до 3 (самое важное): speed , space , safety , debug и compilation-speed .
Также есть функция для оценки кода 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 в качестве промежуточного компилятора или интерпретатора для другого языка.
Следующая программа вычисляет наименьшее количество людей в комнате, для которых вероятность уникальных дней рождения составляет менее 50% ( парадокс дней рождения , где для 1 человека вероятность, очевидно, составляет 100%, для 2 — 364/365 и т. д.). Ответ — 23.
В Common Lisp константы по соглашению заключаются в символы +.
( defconstant +year-size+ 365 ) ( defun birthday-paradox ( probability number-of-people ) ( let (( new-probability ( * ( / ( - +year-size+ number-of-people ) +year-size+ ) probability ))) ( if ( < new-probability 0.5 ) ( 1+ number-of-people ) ( birthday-paradox new-probability ( 1+ number-of-people )))))
Вызов примера функции с использованием REPL (Read Eval Print Loop):
CL-USER > (парадокс дня рождения 1.0 1)23
Мы определяем класс person
и метод для отображения имени и возраста человека. Затем мы определяем группу людей как список person
объектов. Затем мы итерируем по отсортированному списку.
( defclass person () (( name :initarg :name :accessor person-name ) ( age :initarg :age :accessor person-age )) ( :documentation "Класс PERSON со слотами NAME и AGE." )) ( defmethod display (( object person ) stream ) "Отображение объекта PERSON в выходном потоке." ( with-slots ( name age ) object ( format stream "~a (~a)" name age ))) ( defparameter *group* ( list ( make-instance 'person :name "Боб" :возраст 33 ) ( make-instance 'person :name "Крис" :возраст 16 ) ( make-instance 'person :name "Эш" :возраст 23 )) "Список объектов PERSON." ) ( dolist ( person ( sort ( copy-list *group* ) #' > :key #' person-age )) ( display person *standard-output* ) ( terpri ))
Он выводит три имени в порядке убывания возраста.
Боб (33)Ясень (23)Крис (16)
Демонстрируется использование макроса LOOP:
( defun power ( x n ) ( цикл с результатом = 1 while ( plusp n ) when ( oddp n ) do ( setf result ( * result x )) do ( setf x ( * x x ) 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 ( file predicate ) "Возвращает список строк в файле, для которых предикат, примененный к строке, возвращает T." ( with-open-file ( stream file ) ( loop for line = ( read-line stream nil nil ) while line when ( funcall predicate line ) collect it )))
Функция AVAILABLE-SHELLS вызывает указанную выше функцию LIST-MATCHING-LINES с именем пути и анонимной функцией в качестве предиката. Предикат возвращает имя пути оболочки или NIL (если строка не является именем файла оболочки).
( defun available-shells ( &optional ( file #p"/etc/shells" )) ( list-matching-lines file ( 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 соответственно). В отличие от многих более ранних Lisp, Common Lisp (например, Scheme ) по умолчанию использует область действия лексической переменной как для интерпретируемого, так и для компилируемого кода.
Большинство систем Lisp, чьи разработки способствовали Common Lisp, такие как ZetaLisp и Franz Lisp, использовали динамически ограниченные переменные в своих интерпретаторах и лексически ограниченные переменные в своих компиляторах. Scheme ввел в Lisp исключительное использование лексически ограниченных переменных; вдохновение из ALGOL 68. CL также поддерживает динамически ограниченные переменные, но они должны быть явно объявлены как «специальные». Различий в области действия между интерпретаторами и компиляторами ANSI CL нет.
Common Lisp иногда называют Lisp-2 , а Scheme — Lisp-1 , ссылаясь на использование CL отдельных пространств имен для функций и переменных. (На самом деле, в CL много пространств имен, например, для тегов go, имен блоков и loop
ключевых слов). Между сторонниками CL и Scheme существует давний спор по поводу компромиссов, связанных с несколькими пространствами имен. В Scheme (в целом) необходимо избегать присвоения переменным имен, которые конфликтуют с функциями; функции Scheme часто имеют аргументы с именами lis
, lst
, или , lyst
чтобы не конфликтовать с системной функцией list
. Однако в CL необходимо явно ссылаться на пространство имен функции при передаче функции в качестве аргумента, что также является распространенным явлением, как в sort
примере выше.
CL также отличается от Scheme обработкой булевых значений. Scheme использует специальные значения #t и #f для представления истинности и ложности. CL следует старому соглашению Lisp об использовании символов T и NIL, где NIL также обозначает пустой список. В CL любое значение, отличное от NIL, рассматривается как истинное с помощью условных операторов, таких как if
, тогда как в Scheme все значения, отличные от #f, рассматриваются как истинные. Эти соглашения позволяют некоторым операторам в обоих языках служить как предикатами (отвечая на вопрос с булевским значением), так и возвращать полезное значение для дальнейших вычислений, но в Scheme значение '(), которое эквивалентно NIL в Common Lisp, оценивается как истинное в булевом выражении.
Наконец, документы стандартов Scheme требуют оптимизации хвостового вызова , чего стандарт CL не требует. Большинство реализаций CL предлагают оптимизацию хвостового вызова, хотя часто только когда программист использует директиву оптимизации. Тем не менее, общий стиль кодирования CL не благоприятствует повсеместному использованию рекурсии, которое предпочитает стиль Scheme — то, что программист Scheme выразил бы с помощью хвостовой рекурсии, пользователь CL обычно выразил бы с помощью итерационного выражения в do
, dolist
, loop
, или (в последнее время) с помощью iterate
пакета.
См. категорию Реализации Common Lisp .
Common Lisp определяется спецификацией (как Ada и C ), а не одной реализацией (как Perl ). Существует множество реализаций, и стандарт детализирует области, в которых они могут обоснованно различаться.
Кроме того, реализации, как правило, поставляются с расширениями, которые предоставляют функциональные возможности, не предусмотренные стандартом:
Для поддержки расширений Common Lisp в переносимом виде были созданы библиотеки свободного и открытого программного обеспечения , наиболее заметные из которых можно найти в репозиториях проектов Common-Lisp.net [20] и CLOCC (Common Lisp Open Code Collection) [21] .
Реализации Common Lisp могут использовать любую смесь компиляции собственного кода, компиляции байт-кода или интерпретации. Common Lisp был разработан для поддержки инкрементальных компиляторов , компиляторов файлов и блочных компиляторов. Стандартные объявления для оптимизации компиляции (такие как встраивание функций или специализация типов) предлагаются в спецификации языка. Большинство реализаций Common Lisp компилируют исходный код в собственный машинный код . Некоторые реализации могут создавать (оптимизированные) автономные приложения. Другие компилируют в интерпретируемый байт-код , который менее эффективен, чем собственный код, но облегчает переносимость двоичного кода. Некоторые компиляторы компилируют код Common Lisp в код C. Заблуждение, что Lisp является чисто интерпретируемым языком, наиболее вероятно, потому, что среды Lisp предоставляют интерактивную подсказку, и этот код компилируется один за другим, инкрементальным способом. В Common Lisp широко используется инкрементальная компиляция.
Некоторые реализации на базе Unix ( CLISP , SBCL ) могут использоваться в качестве языка сценариев , то есть вызываться системой прозрачно, как это делают интерпретаторы оболочек Perl или Unix . [22]
Common Lisp используется для разработки исследовательских приложений (часто в области искусственного интеллекта ), для быстрой разработки прототипов или для развернутых приложений.
Common Lisp используется во многих коммерческих приложениях, включая сайт интернет-коммерции Yahoo! Store, в котором изначально участвовал Пол Грэм , а затем он был переписан на C++ и Perl . [47] Другие известные примеры включают в себя:
Существуют также приложения с открытым исходным кодом, написанные на Common Lisp, такие как:
{{cite journal}}
: Цитировать журнал требует |journal=
( помощь ){{cite journal}}
: Цитировать журнал требует |journal=
( помощь )Хронологический список книг, опубликованных (или готовящихся к публикации) о Common Lisp (языке) или о программировании на Common Lisp (особенно о программировании ИИ).