Common Lisp

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 12:17, 24 января 2017.
Common Lisp
Спроектировано комитет X3J13
Первый   появившийся 1984, стандартизирован ANSI в 1994
Печать дисциплины сильная, динамическая, с опциональными декларациями типов
OS кроссплатформенный
Главная реализация
Allegro Common Lisp, ABCL, CLISP, Clozure CL, CMUCL, Corman Common Lisp, ECL, LispWorks, Scieneer CL, SBCL
Под влиянием
Lisp, Lisp Machine Lisp, MacLisp, InterLisp, Scheme
Влияние
Clojure, Factor, Dylan, EuLisp, ISLisp, SKILL, Stella, SubL

Common Lisp (сокр. CL) — диалект языка Лисп, стандартизированный ANSI[1]. Был разработан с целью объединения разрозненных на тот момент диалектов Лиспа. Доступно несколько реализаций Common Lisp, как коммерческих, так и cвободно распространяемых.

Common Lisp — мультипарадигменный язык программирования общего назначения. Он поддерживает комбинацию процедурного, функционального и объектно-ориентированного программирования.

Common Lisp включает в себя CLOS; систему Lisp макросов, позволяющую вводить в язык новые синтаксические конструкции, использовать техники метапрограммирования и обобщённого программирования.

История

После предложения управляющего из ARPA Боба Энгельмора разработать единый общий стандарт Лисп диалекта, началась работа над Common Lisp'ом в 1981 году. Многое в первоначальной структуре языка было разработано посредством связи через электронные письма. Первый обзор Common Lisp'а был произведён Гайем Льюисом Стилом на ACM симпозиуме по Лиспу и функциональному программированию 1982 года.

Первая документация по языку была опубликована в 1984 году как «Язык Common Lisp, первая редакция». Вторая редакция, выпущенная в 1990 году, включала много изменений, произведённых ANSI над языком в процессе стандартизации. Конечную же редакцию стандарта опубликовали в 1994 году.

С тех пор никаких обновлений стандарта выпущено не было, а различные расширения и улучшения Common Lisp'а (Unicode, параллелизм, основанные на CLOS ввод и вывод) обеспечиваются реализацией и библиотеками (многие доступны посредством Quicklisp).

Синтаксис

Common Lisp использует S-выражения для обозначения как кода, так и данных. Вызовы функций и макросов являются списками, где первый элемент, или голова списка, является именем функции, а остальные, — «хвост» списка, — аргументами.

(+ 2 2)                       ; складывает 2 и 2, возвращая результат 4. 
(- 8 3)                       ; вычитает из восьми три, возвращая результат 5. 
(reverse "Здравствуй, мир!")  ; переворачивает строку, возвращая «"!рим ,йувтсвардЗ"»

;; определения переменных и функций:
(defvar *x*)                  ; Определяет переменную *x*, не присваивая ей какого-либо
                              ; значения. Две звёздочки, являющиеся частью имени — принятый 
                              ; стандарт для именования глобальных переменных.

(setf *x* 42.1)               ; присваивает переменной *x* значение с плавающей запятой 42,1.

(defun square (x)             ; Определение функции square, получающей один аргумент
  (* x x))                    ; и возвращающей его квадрат.

(square *x*)                  ; вызывает функцию square, передавая ей значение переменной *x*
                              ; и возвращает её квадрат (1772,41).

(let ((a 3)(b 4)) (+ a b))    ; Функция let создаёт локальные переменные, присваивает им значения
                              ; (в данном случае переменной a присваивается значение 3, а b - 4), 
                              ; после чего вычисляет и возвращает результат функции
                              ; (в данном случае 7). Переменные локальны, следовательно
                              ; попытка посчитать значение (+ a b) вне тела let приведёт к ошибке.

Типы данных

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

Числовые типы включают в себя целые, дроби, числа с плавающей запятой и комплексные числа. Common Lisp даёт возможность использовать большие числа для представления любых величин с большей точностью.

|Литеры в Common Lisp не ограничены ASCII. Большинство современных реализаций поддерживают Юникод.

Понятие Символа довольно близко Лиспу, хотя, практически неизвестно в таком виде в других языках. В Лиспе символ — это уникальный объект из данных, включающих в себя: имя, значение, функцию, список свойств и пакет (пространство имён). Символы в Лиспе часто используются так же, как идентификаторы в других языках: для хранения значения переменной. Однако, у них есть множество других применений. Обычно, когда символу присваивается значение, оно тут же им и возвращается. Некоторые символы присваивают значение сами себе, так, например, булевы значения представлены в качестве двух самоопределяющихся символов, — T и NIL.

Есть несколько функций для округления скалярных типов разными способами. Функция round округляет аргумент до ближайшего целого числа, а если число стоит «посередине», то до ближайшего чётного. Функция truncate округляет числа по направлению к нулю. Функции floor («пол») и ceiling («потолок») округляют к ближайшему меньшему и ближайшему большему числу соответственно. Все функции возвращают дробную часть в качестве вторичного результата.

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

Последовательностями в Common Lisp являются списки, векторы, битовые векторы и строки. Есть множество операций, которые могут работать с любым типом последовательности.

Как и почти во всех других диалектах Лиспа, списки в Common Lisp состоят из точечных пар (conses). cons — это структура данных с двумя слотами: car и cdr. Список является связной цепочкой точечных пар (или может быть пустым). car каждой пары ссылается на элемент списка (возможно, другой список). cdr каждой пары ссылается следующую пару, за исключением последней пары в списке, чей cdr ссылается на значение nil. Точечные пары также легко могут быть использованы для реализации деревьев и других сложных структур данных; хотя вместо этого, как правило, рекомендуется использовать структуры или экземпляры классов. Кроме того, можно создать циклическую структуру данных с точечными парами.

Common Lisp поддерживает многомерные массивы и может динамически изменять размер у регулируемых массивов, если потребуется. Многомерные массивы могут быть использованы для матричных вычислений. Вектор является одномерным массивом. Массивы могут содержать любой тип в качестве элемента (даже смешивать разные типы в одном массиве) или могут быть специализированы, чтобы содержать только определенный тип. Обычно поддерживаются только несколько типов. Многие реализации могут оптимизировать функции работы с массивами, когда массив типоспециализирован. Есть два стандартных типоспециализированных массива: строка, являющаяся вектором символов (char), и вектор битов.

Хеш-таблица хранит отображение между объектами. Любой объект может быть использован в качестве ключа или значения. Хэш-таблицы автоматически изменяют размер по мере необходимости.

Пакеты — коллекции символов, используемых в основном для разделения частей программы на пространства имен. Пакет может экспортировать некоторые символы, пометив их как часть открытого интерфейса. Пакеты могут использовать другие пакеты.

Структуры, аналогичные структурам в Си и записям в Паскаль, представляют произвольные сложные структуры данных с любым количеством и типом полей (называемых слотами). Структуры позволяют одиночное наследование.

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

Функции

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))

Определение функций

Макрос defun определяет функцию. defun принимает имя функции, имена параметров и тело функции:

 (defun square (x)
   (* x x))

Определения функций могут включать директивы компилятора, известные как declarations, которые дают подсказки компилятору об оптимизациях или о типах аргументов. Также могут быть добавлены строки документации (docstrings), которые Лисп может использовать для предоставления документации:

 (defun square (x)
   "Calculates the square of the single-float x."
   (declare (single-float x) (optimize (speed 3) (debug 0) (safety 1)))
   (the single-float (* x x)))

Анонимные функции определяются с помощью lambda, например, (lambda (x) (* x x)) — функция возведения в квадрат. Стиль программирования на Лиспе подразумевает частое использование функций высшего порядка, в которые удобно передавать анонимные функции как аргументы.

Локальные функцие могут быть объявлены с помощью flet и labels.

 (flet ((square (x)
          (* x x)))
   (square 3))

Существует несколько других операторов, относящихся к определению и манипуляциям с функциями. Например, функции могут быть скомпилированы с compile оператором. (Некоторые Лисп системы выполняют функции, используя по умолчанию интерпретатор, если не указано compile; другие компилируют каждую функцию).

Возможности

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

Определения

Параметры, которые определяют сферу в Common Lisp включают:

  • расположение ссылки в выражении. Если это крайнее левое положение соединения, оно относится к специальным оператором или макрос или функцию связывания, в противном случае к переменной связывания или что-то другое.
  • вид выражения, в котором ссылка происходит. Например, (go х) означает контроль передачи на метку х, в то время как (print х) относится к переменной х. Оба прицелы х могут быть активны в той же области текста программы, так как tagbody метки находятся в отдельном пространстве имен от имен переменных. Особая форма или макро форма имеет полный контроль над значениями всех символов в его синтаксиса. Например, в (defclass x (a b) ()), определение класса, (a b) представляет собой список базовых классов, так что эти имена ищется в пространстве имен классов, а х не является ссылкой на существующую привязку, но имя нового класса являются производными от а и b. Эти факты возникают чисто от семантики DEFCLASS. Единственный общий факт об этом выражении является то, что defclass относится к макро связывания; все остальное до defclass.
  • расположение ссылки в тексте программы. Например, если ссылка на переменную х заключена в связывающей конструкции let, которая определяет привязку для х, то ссылка находитмя в рамках созданного.
  • для ссылки на переменную, или символ переменной, локально или глобально, объявлены специально. Это определяет, будет ли решена ссылка в лексической среде, или в динамичной среде.
  • конкретный экземпляр среды, в которой будет решена ссылка. Сущетсвует словарь, который отображает символы для привязки. Каждый вид ссылки использует свой собственный вид окружающей среды. Ссылки на лексических переменных разрешаются в лексической среде, и так далее. Более одной среды могут быть связаны с тем же самым в качестве ссылки. Например, благодаря рекурсии или использование нескольких потоков, несколько активаций той же самой функции могут существовать одновременно. Эти активаций одни и те же текст программы, но каждый из них имеет свою собственную лексическую среду.

Среда разработки

Глобальная

В некоторых средах в Лиспе глобально распространены. Например, если новый тип определен, то он известен повсюда после этого. Ссылки на этот тип ищут в этой глобальной среде.

Динамическая

Еще одним типом среды в Common Lisp является динамическая среда. Привязки, установленные в этой среде имеют динамическую степень, что означает, что связывание устанавливается в начале выполнения какой-либо конструкции, такие как блок let, и исчезает, когда эта конструкция заканчивает выполнение: ее срок службы привязаны к динамической активации и деактивации блока. Тем не менее, динамическое связывание не только видим внутри этого блока; оно также видно всем функциям, вызванным из этого блока. Этот тип видимости известен как неопределенный характер. Привязанности, которые демонстрируют динамическую степень (время жизни привязано к активации и деактивации блока) и неопределенный объем (видимый для всех функций, которые вызываются из этого блока), как говорят, имеют динамическую область видимости.

Common Lisp поддерживает динамически контекстные переменные, которые также называются специальными переменными. Некоторые другие виды привязок обязательно имеют динамическую областью видимости. Функция привязки не может быть динамической областью видимости с использованием flet (который только обеспечивает лексически ограниченные функции привязок), но действуют объекты (объект первого уровня в Common Lisp) могут быть назначены динамически контекстные переменные, связанными с использованием let в динамическом диапазоне, затем вызывается с помощью funcall или APPLY.

Динамическая сфера является чрезвычайно полезной, поскольку он добавляет ссылочную ясности и дисциплины к глобальным переменным. Глобальные переменные могут выступать в качестве потенциальных источников ошибок.

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

В Common Lisp реализаций, которые поддерживают многопоточность, динамические области видимости являются специфическими для каждого потока исполнения. Таким образом, специальные переменные служат в качестве абстракции для потока локального хранилища. Если один поток выполняет повторную привязку специальную переменную, эта перекомпоновка не оказывает никакого влияния на эту переменную в других потоках. Значение, сохраненное в привязке может быть получено только в потоке. Если каждая нить связывает некоторую специальную переменную *x*, то *х* ведет себя как локальная переменная.

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

В библиотеке Common Lisp есть много стандартных специальных переменных. Например, все стандартные потоки ввода / вывода хранятся в креплениями верхнего уровня известных специальных переменных. Стандартный выходной поток сохраняется в *стандартный-вывод*.

Предположим, что функция foo записывает в стандартный вывод:

  (defun foo ()
    (format t "Hello, world"))

Для того, чтобы вывод был в виде строки символов, *стандартный-вывод* может быть привязан к строке и записывается как:

  (with-output-to-string (*standard-output*)
    (foo))
 -> "Hello, world" ; в качестве выхода выводится строка.

Лексическая

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

Теги в TAGBODY имеют лексическую область видимости. Выражение (GO Х) является ошибочным, если он на самом деле не встроено в TAGBODY, который содержит метку X. Однако, связанная метка исчезает, когда TAGBODY завершает свое выполнение, так как она имеют динамическую степень. Если этот блок кода повторно вводится с вызовом лексической звершения, тоон является недействительным для тела, пытаясь передать управление с помощью тега GO:

  (defvar *stashed*) ;; will hold a function

  (tagbody
    (setf *stashed* (lambda () (go some-label)))
    (go end-label) ;; skip the (print "Hello")
   some-label
    (print "Hello")
   end-label)
  -> NIL


Когда TAGBODY выполняется, он сначала оценивает форму SETF, который хранит функцию в специальной переменной *exaple*. Тогда (метка с переходом в конец кода) передает управление в конец метки, пропуская код (print "Hello"). Конечная метка в конце tagbody, то tagbody заканчивается, уступая NIL.

Реализации

Common Lisp отличается от таких языков, как C#, Java, Perl, Python тем, что он определяется своим стандартом и не существует его единственной или канонической реализации. Любой желающий может ознакомиться со стандартом и создать свою собственную реализацию. Common Lisp автоматически признаёт эти типы как равные.

Таблица сравнения основных реализаций[2]
Название Поддерживаемые платформы Компиляция Возможности Лицензия
CLISP[3] Windows, Mac,*nix Байт-код, JIT Маленький размер образа лисп-системы. Очень эффективная длинная целочисленная арифметика. Возможность создания исполняемых файлов. FFI(интерфейс для вызова низкоуровневых функций(функций из библиотек, написанных на Си и т. п.) и для оперирования «неуправляемой» памятью). Функции обратного вызова(интеграция с «родным» кодом платформы). GNU GPL
CMUCL[4] Linux, FreeBSD, Solaris, Darwin Байт-код, машинный код Высококачественный компилятор в машинный код. FFI. Функции обратного вызова(интеграция с «родным» кодом платформы). Общественное достояние с частями под BSD License
ECL Windows, *nix, Mac OS X Байт-код, машинный код через C Хорошая интеграция с программами на Си и с «родным» кодом платформы(FFI, функции обратного вызова, возможность создания «родных» для платформы динамических и статических библиотек). Возможность создания исполняемых файлов. «Родная» многопоточность на всех поддерживаемых платформах. GNU GPL с частями под другими лицензиями
Clozure CL (бывший OpenMCL) Linux/PPC, Linux/X86-64, Darwin/PPC, Darwin/X86-64, FreeBSD/X86-64, Windows Машинный код Быстрый компилятор. Мощный и удобный FFI. Функции обратного вызова (интеграция с «родным» кодом платформы). Возможность создания исполняемых файлов. «Родная» многопоточность на всех поддерживаемых платформах. LGPL
SBCL[5] Linux, BSD, Solaris, Mac OS X (Darwin), Windows (экспериментально) Машинный код Высококачественный компилятор в машинный код. Возможность создания исполняемых файлов. FFI. Функции обратного вызова(интеграция с «родным» кодом платформы). «Родная» многопоточность на Linux, Solaris 10 и Mac OS X. Общественное достояние с частями под MIT License и BSD License
ABCL JVM байт-код JVM Интерфейс к платформе Java. Многопоточность. Платформонезависимость. LGPL
Allegro Common Lisp[6] Windows, Unix, Linux, Mac OS X Машинный код http://franz.com/products/allegrocl/ Коммерческая, доступна ограниченная демоверсия
LispWorks[7] Windows, *nix, Mac OS X Машинный код Высококачественная IDE. CAPI — библиотека для создания пользовательского интерфейса. Хорошая интеграция с программами на Си и с «родным» кодом платформы(FFI, функции обратного вызова, возможность создания «родных» для платформы динамических и статических библиотек). Возможность создания исполняемых файлов. «Родная» многопоточность на всех поддерживаемых платформах. Полный список возможностей: http://www.lispworks.com/products/features.html Коммерческая, доступна ограниченная демоверсия
Corman Common Lisp Windows Машинный код http://www.cormanlisp.com/features.html С 2015 года — MIT License. Изначально распространялся как Shareware, с доступным исходным кодом системы (исключая IDE)

Примечания

  1. ANSI INCITS 226—1994 (R2004), ранее X3.226-1994 (R1999)
  2. Clips [Электронный ресурс]: ANSI Common Lisp Implementation / Дата обращения: 23.12.2016. — Режим доступа: http://www.cliki.net/Common%20Lisp%20implementation
  3. The Common Lips Wiki [Электронный ресурс]: Common Lisp implementation / Дата обращения: 23.12.2016. — Режим доступа: http://clisp.sourceforge.net/
  4. CMUCL [Электронный ресурс]: Free Common Lisp implementation / Дата обращения: 23.12.2016. — Режим доступа: http://www.cons.org/cmucl/
  5. Sourceforge [Электронный ресурс]: Steel Bank Common Lisp / Дата обращения: 23.12.2016. — Режим доступа: http://sbcl.sourceforge.net/ Steel Bank Common Lisp (англ.)]
  6. Franz Inc. [Электронный ресурс]: Franz Inc. Main Page / Дата обращения: 13.01.2017. — Режим доступа: http://www.franz.com/ Allegro CL — разработчик Franz Inc. (англ.)
  7. LispWorks. Разработчик — LispWorks, LLC (ранее Xanalys, Inc.)