История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...
Своеобразие русской архитектуры: Основной материал – дерево – быстрота постройки, но недолговечность и необходимость деления...
Топ:
Выпускная квалификационная работа: Основная часть ВКР, как правило, состоит из двух-трех глав, каждая из которых, в свою очередь...
История развития методов оптимизации: теорема Куна-Таккера, метод Лагранжа, роль выпуклости в оптимизации...
Характеристика АТП и сварочно-жестяницкого участка: Транспорт в настоящее время является одной из важнейших отраслей народного...
Интересное:
Распространение рака на другие отдаленные от желудка органы: Характерных симптомов рака желудка не существует. Выраженные симптомы появляются, когда опухоль...
Наиболее распространенные виды рака: Раковая опухоль — это самостоятельное новообразование, которое может возникнуть и от повышенного давления...
Финансовый рынок и его значение в управлении денежными потоками на современном этапе: любому предприятию для расширения производства и увеличения прибыли нужны...
Дисциплины:
2018-01-03 | 434 |
5.00
из
|
Заказать работу |
|
|
>(defun checkA (at lst) (cond ((null lst) nil) ((eql at (car lst)) T) (T (checkA at (cdr lst))) )) CHECKA >(checkA 'A '(b c)) NIL >(checkA 'A '(a b c)) T |
Удаление из списка N-ного элемента
>(defun delAt (pos lst) (cond ((null lst) nil) ((eql pos 0) (cdr lst)) (T (cons (car lst) (delat (- pos 1) (cdr lst)))) ) ) DELAT >(delAt 2 '(a b c d)) (A B D) >(delAt 5 '(a b c d)) (A B C D) |
Добавление в список элемента в позицию N
>(defun insAt (pos lst at) (cond ((null lst) (cons at nil)) ((eql pos 0) (cons at lst)) (T (cons (car lst) (insAt (- pos 1) (cdr lst) at))) ) ) INSAT >(insat 3 '(a b c d e) 'f) (A B C F D E) >(insat 30 '(a b c d e) 'f) (A B C D E F) |
В приведённых примерах рассматривалась рекурсия как таковая, однако в Lisp различают несколько вариантов рекурсии. Рассмотрим их подробнее.
· Параллельная рекурсия. Такая рекурсия возникает, когда в теле некоторой функции f1 содержится вызов функции f2, в качестве аргументов которой используется вызов функции f1.
· Взаимная рекурсия. Возникает при вызове в теле функции f1 функции f2, в теле которой, в свою очередь, существует вызов функции f1.
· Вложенная рекурсия - возникает, когда функция в своём теле вызывает саму себя, используя себя же в качестве параметра этого вызова.
Очевидно, что функции INSAT, DELAT и CHECKA, рассмотренные выше, имеют параллельную рекурсивную схему. В качестве ещё одного примера параллельной рекурсии приведём следующий код, осуществляющий инверсию списка любой глубины вложенности:
>(defun ReversePlus (List) (cond ((atom List) List) ((null (cdr List)) (cons (ReversePlus (car List)) nil)) (T (append (ReversePlus (cdr List)) (ReversePlus (cons (car List) nil)))))) REVERSEPLUS >(ReversePlus '(1 (2 3) (4 (5 6)) 7)) (7 ((6 5) 4) (3 2) 1) |
Тем самым, мы видим, что функция развернула списки в обратном порядке на всех своих уровнях. То же самое можно выполнить с помощью взаимной рекурсии:
>(defun ReversePlus (List) (cond ((atom List) List) (T (Change List nil)) ) ) REVERSEPLUS >(defun Change (List Result) (cond ((null List) Result) (T (change (cdr List) (cons (ReversePlus (car List)) Result))) ) ) CHANGE >(ReversePlus '(1 (2 3) (4 (5 6)) 7)) (7 ((6 5) 4) (3 2) 1) |
Вложенная рекурсия является достаточно редкой. Обычно этот вид рекурсии иллюстрируется с помощью специализированных математических функций, таких как функции Аккермана (Вирт, 1986). Такая функция определяется следующим образом:
|
A(0,n)=n+1;
A(m,0)=A(m-1,1); (m>0)
A(m,n)=A(m-1,A(m,n-1)); (m,n>0)
Очевидно, что функция Lisp, вычисляющая функцию Аккермана, будет построена в соответствии с определением:
> (defun akk (m n) (cond ((eql m 0) (+ 1 n)) ((eql n 0) (akk (- m 1) 1)) (T (akk (- m 1) (akk m (- n 1)))))) AKK >(akk 2 2) |
Автор рекомендует выполнить вызов (akk 2 2) после вызова (trace akk) и рассмотреть дерево функций, имеющее достаточно любопытный вид.
Функционалы
В математике под функционалом понимается функция, имеющая своим аргументом другую функцию. Соответственно, в языках программирования, в том числе в Lisp, функционалом является функция, принимающая некоторыми своими аргументами "процедурный" тип данных.
Первым делом параметр-функцию надо передать, подавив её вычисление. Для этого используется функция FUNCTION
function | (function функция аргументы) Функция позволяет подавить вычисление своего параметра. Действует аналогично QUOTE, но для аргументов функционального типа. В большинстве случаев функциональный параметр используется вместе с QUOTE, однако FUNCTION может быть использована для указания, что в качестве параметра следует ждать именно функцию. |
Другая задача - вычислить переданный таким образом аргумент.
funcall | (funcall функция) Обратная FUNCTION функция, позволяющая отменить подавление вычисления. |
>(FUNCALL (FUNCTION (LAMBDA (X Y) (* X Y)) ) 2 4) |
В приведённом примере с помощью LAMBDA задаётся функция, принимающая два аргумента и возвращающая их произведение. Вычисление этой функции подавляется с помощью FUNCTION (иначе мы получили бы ошибку о недопустимом числе аргументов). Затем над результатом, а именно функциональным аргументом, выполняется FUNCALL, с указанием двух аргументов - 2 и 4. Как видно, результатом было искомое произведение.
|
Приведём пример использования функционала. Например, мы хотим вычислить экстремум в некоторой последовательности:
>(defun extremum (lst fn el) (cond ((eq (cdr lst) nil) (car lst)) ((funcall fn (car lst) (extremum (cdr lst) fn el)) (car lst)) (T (extremum (cdr lst) fn el)))) EXTREMUM |
Теперь вычислим минимум и максимум в последовательностях:
>(extremum '(6 -3 1 2 3) (function <) 1000) -3 >(extremum '(6 -3 1 2 3) (function >) -1000) |
Таким образом, в зависимости от переданного в функцию функционального аргумента (< или >) мы вычисляем минимум или максимум. Третьим параметром функции является условно наибольшее и наименьшее число. В принципе, можно таковым считать первый символ последовательности (пример апеллирует к определённой ранее функции EXTREMUM).
>(defun extremum2 (lst arg) (extremum (cdr lst) arg (car lst))) EXTREMUM2 >(extremum2 '(1 2 3) (function <)) >(extremum2 '(1 2 3) (function >)) |
В Lisp присутствует также некоторое количество стандартных функций с функциональными аргументами, которые в основном также как и в нашем примере реализуют работу с последовательностями. Различают два основных класса функционалов - отображающие и применяющие. Применяющими функционалами являются уже известный нам FUNCALL, а также APPLY.
apply | (apply функция аргумент) Функционал применяет некоторую функцию к аргументам, которые заданы списком в качестве второго параметра APPLY |
>(apply 'car '((a b))) A >(funcall 'car '((a b))) (A B) |
Отметим, что не все функции могут использоваться в качестве функциональных аргументов, например, использование OR и AND даёт ошибку (интерпретатор аргументирует это тем, что OR, AND и некоторые другие функции имеют некую специальную реализацию).
Общая идея, объединяющая применяющие функционалы, заключается в применении функций к спискам аргументов. Отображающие функционалы предназначены для повторения вычисления функции на элементах списка.
mapcar | (mapcar функция список) Функция, позволяющая выполнить некоторое вычисление последовательно надо всеми элементами списка. Результатом является список, построенный из результатов применения функционального аргумента к элементам списка. |
>(mapcar 'null '(() (a b) (nil c d))) (T NIL NIL) >(mapcar '(lambda (x) (cons x nil)) '(a b c)) ((A) (B) (C)) |
maplist | (maplist функция список) Функция, позволяющая выполнить некоторое вычисление последовательно над хвостовыми частями списка. Сперва вычисляется функция от списка, затем (CDR список), затем (CDDR список), (СDDDR список) и так далее. Результатом является список, построенный из результатов применения функционального аргумента к хвостовым элементам списка. |
|
>(maplist '(lambda (x) x) '(a b c)) ((A B C) (B C) (C)) |
С помощью таких функционалов можно существенно сократить тексты функций в некоторых специальных случаях. Например, так выглядит вычисление максимума:
>(defun max2 (lst) (progn (setq x (car lst)) (mapcar '(lambda (y) (if (< x y) (setq x y))) lst) x)) MAX2 >(max2 '(1 2 3)) |
mapcan mapcon | (mapcan функция список) (mapcon функция список) Аналоги MAPCAR и MAPLIST, однако при своей работе строят список, используя функцию NCONC, т.е. потенциально структура исходных данных может разрушиться. Эти функционалы называются объединяющими, и одно из основных проявлений их особенностей заключается в уничтожении всех вхождений NIL в результирующий список. |
>(setq x '(A B C)) (A B C) >(mapcar 'list x) ((A) (B) (C)) >(mapcan 'list x) (A B C) >(maplist 'list x) (((A B C)) ((B C)) ((C))) >(mapcon 'list x) ((A B C) (B C) (C)) |
Используем способность объединяющих функционалов уничтожать NIL для написания функции, проверяющей вхождение элемента в список:
>(defun isinlist (lst el) (car (mapcan (function (lambda (y) (if (eql y el) '(t) nil))) lst))) ISINLIST >(isinlist '(1 2 3) 2) T >(isinlist '(1 2 3) 4) NIL |
mapc mapl | (mapc функция список) (mapl функция список) Аналоги MAPCAR и MAPLIST, однако эти функции не строят никакого нового результата, а просто возвращают исходный список. Таким образом, для них может быть важен только некоторых побочный результат, в том случае, если использованная в качестве параметра функция содержит в той или иной форме присвоение, вывод на экран и т.п. |
В качестве примера такого побочного действия рассмотрим вывод на экран.
>(mapc 'print '(A B C)) A B C (A B C) |
Таким образом, все элементы списка были по очереди выведены, а в качестве результата вернулся исходный список. Очевидно, этот же эффект мог быть достигнут и MAPCAR, и MAPCAN, однако при этом не тратились ресурсы на конструирование новых структур в памяти.
Функционалы не обязательно работают с функциями одного аргумента. Если аргументов больше, требуется подача на вход нескольких списков. В качестве примера рассмотрим преобразование двух списков в третий, содержащий в себе произведения элементов исходных списков, находящихся в одинаковых позициях.
|
>(mapcar '* '(1 2 3) '(4 5 6)) (4 10 18) |
Или, например, попарное объединение элементов двух списков:
>(mapcar 'list '(1 2 3) '(4 5 6)) ((1 4) (2 5) (3 6)) |
Расширением этого примера может быть вычисление скалярного произведения двух векторов (в примере [1 2 3] и [4 5 6]):
>(apply '+ (mapcar '* '(1 2 3) '(4 5 6))) |
В случае, если у списков параметров не совпадают размеры, лишние элементы списков будут просто проигнорированы:
>(mapcar 'list '(1 2 3) '(4 5)) ((1 4) (2 5)) |
Для MAPC и MAPL возвращаемым значением всегда будет первых аргумент-список.
Макросы
Макросом называется специальная функция, которая вычисляется не сразу а в два этапа: сперва вычисляется её тело, и только потом тело применяется к аргументам. Это бывает полезно, например, если мы хотим сформировать последовательность вычислений программно, а затем эту последовательность выполнить.
defmacro | (defmacro имя лямбда-список тело) Определяет макрос. Синтаксис определения макроса в целом сходен с синтаксисом определения функции, однако при вызове макроса не происходит предварительное вычисление его аргументов. |
Первый этап вычисления макроса называется этапом расширения. На этом этапе макрос преобразуется в вычисляемую форму. На втором этапе эта форма вычисляется и возвращается результат.
В качестве примера макроса определим функцию SETQQ, которая была бы аналогична SETQ, но не требовала явного подавления вычисления второго аргумента.
>(defmacro setqq (x y) (list 'setq x (list 'quote y))) SETQQ >setqq x (a b c)) (A B C) >x (A B C) |
macroexpand | (macroexpand макровызов) Функция возвращает результат расширения макроса. Имеет важное значение для тестирования. |
Для того, чтобы посмотреть, какая именно форма была вычислена, применим следующий вызов:
>(macroexpand '(setqq x (a b c))) (SETQ X '(A B C)) T |
В том, что, несмотря на выведенное на печать значение T, функция возвращает именно результат раскрытия макроса, можно убедиться на следующем примере:
>(setq x (macroexpand '(setqq x (a b c)))) (SETQ X '(A B C)) >x (SETQ X '(A B C)) |
Контекст вычисления макроса отличается от контекста вычисления функции. На этапе раскрытия в макросе могут использоваться все статические связи, созданные при его определении, однако когда происходит вычисление, текст макроса полностью подменяется на результат расширения его определения, и все статические связи становятся недоступны.
В принципе, макросы могут при расширении давать формы, содержащие в себе макровызовы, в том числе и рекурсивные. Эти формы будут расширены и вычислены на этапе вычисления основного макроса.
|
Макросы могут быть рекурсивными. Это означает, что расширение макроса даёт нам новый макрос. Рекурсивные макросы сродни рекурсивным функциям, за исключением того, что вычисление нового макровызова также производится в два этапа. В качестве примера рекурсивного макроса приведём следующий макрос, считающий сумму чисел в списках специального вида:
>(DEFMACRO UNWRAP (X Y) (cond ((atom Y) (list '+ X Y)) (T (list '+ X (list 'UNWRAP (car y) (cadr y)))))) UNWRAP >(unwrap 1 2) >(unwrap 1 (2 3)) >(unwrap 1 (2 (3 4))) >(macroexpand '(unwrap 1 (2 (3 4)))) (+ 1 (UNWRAP 2 (3 4))) |
Естественно, отладка подобных макросов является достаточно трудоёмкой задачей.
Также как и для функций, для параметров макросов можно использовать зарезервированные слова &OPTIONAL, &KEY, &REST и &AUX. Дополнительно можно использовать свойство параметра &WHOLE, которое свяжет параметр со всей формой макровызова. Особенно полезно это в макросах, способных динамически менять свой код с помощью псевдофункций, таких как RPLACA, RPLACD, SETF м другие. В качестве примера приведём макрос, который сам себя превращает в функцию CAR:
>(defmacro frst (arg &whole call) >(rplaca call 'car)) FRST >(frst '(1 2)) |
С макросами тесно связан механизм обратной блокировки, позволяющий упростить их запись. Обратная блокировка изображается символом ` (апостроф, наклонённый вправо) и отличается от обычной тем, что внутри такого выражения можно управлять вычислением или невычислением секций. Например, запятая указывает, что следующее выражение должно быть вычислено. Символ @ позволяет включить идущее после него выражение в результирующий список.
>`(a,(+ 1 2) 3) (A 3 3) >(setq x '(+ 1 2)) (+ 1 2) >`(a,@x 3) (A + 1 2 3) |
Ввод и вывод
Одной из основных задач для каждого языка программирования является организация взаимодействия с пользователем. В Lisp для этой цели предусмотрены функции ввода-вывода для стандартного устройства, а также функции работы с файлами.
read | (read [поток]) Функция читает выражение из консоли и возвращает его в качестве результата. Функция никак не показывает пользователю, что ожидается ввод. В этой функции и далее: если поток не указан, чтение (или для других функций запись) производится со стандартного устройства ввода - консоли. |
>(read) >>a+b A+B |
Здесь при вызове функции READ пользователь ввёл выражение A+B (символ >> символизирует приглашение к вводу и не должен переноситься в текст при попытке выполнить пример). Результатом вычисления явилось собственно введённое выражение.
В качестве примера рассмотрим вычисление суммы от двух введённых чисел:
>(* (read) (read)) >>3 >>8 >(* (read) (read)) >>3 8 |
Иными словами, Lisp, как и большинство языков высокого уровня, выполняет синтаксический разбор ввода с консоли и выделяет параметры, идущие через пробел. При этом READ вводит именно выражения, т.е. если бы в примере мы ввели, например, (+ 5 7) (- 6 4) то этот ввод также был бы распознан как два параметра, другое дело что подобное выражение недопустимо в рамках примера. Модифицируем пример следующим образом:
>(* (eval (read)) (eval (read))) >>(+ 5 7) (- 6 4) |
Некоторые символы в Lisp при вводе указывают интерпретатору на выполнение определённых действий (например, скобки, пробел, точка). Lisp хранит информацию о таких символах в специальной таблице, называемой таблице чтения. Эти символы называются макрознаками, и их набор может быть изменён (вследствие чего, естественно, может измениться и синтаксис языка Lisp).
set-macro-character | (set-macro-character символ функция) Функция сопоставляет символу некоторую функцию, обрабатывающую этот символ при синтаксическом разборе выражений. Функция разбора должна иметь два аргумента - поток чтения и собственно символ, который мы обрабатываем. |
Следующий пример заставляет Lisp интерпретировать символ % так же, как апостроф (т.е. как функцию QUOTE)
>(SET-MACRO-CHARACTER #\% (lambda (stream char) (list 'quote (read)))) T > %(A) (A) |
print prin1 princ | (print выражение [поток]) (prin1 выражение [поток]) (princ выражение [поток]) Функция выводит данные на консоль. PRINT выводит данные, печатает пробел и переводит строку. В различных диалектах указанные действия производятся в различной последовательности. Кроме того, иногда функция не печатает пробел. PRIN1 работает точно также, но не переводит строку. PRINC работает также, как и PRIN1, но преобразует при выводе некоторые типы данных (например, строковые) к более приятному для восприятия виду. |
>(progn (setq data "a b c d") (print data) (prin1 data) (princ data)) "a b c d" "a b c d"a b c d "a b c d" |
Как видно из примера, в Allegro Common Lisp функция PRINT сперва перевела строку, а затем вывела данные и пробел. Затем функция PRIN1 вывела те же самые данные, но строку не перевела и пробел не напечатала. Наконец, PRINC вывела всё ту же самую строку, но при выводе исключила кавычки. Однако, результатом этой функции, как мы видим, является всё та же строка.
terpri | (terpri [поток]) Функция переводит строку в стандартном устройстве вывода. |
format | (format поток образец [аргументы]) Функция выводит строку в соответствии с образцом. Если вы качестве потока задаётся Т, то вывод производится на экран, в противном случае необходимо указать ассоциированный с файлом и открытый поток вывода. В качестве образца в функцию подаётся строка, определяющая формат вывода (в чём-то сходная со строкой форматирования функции printf языка С). Значения управляющих символов в строке даны в таблице 1. Текст в образце, не являющийся управляющими символами, также печатается. Функция возвращает в качестве своего значения nil. |
Таблица 1
Символы управления выводом функции format
Символ | Смысл |
~% | Перевод строки |
~S | Вывод очередного аргумента функцией PRIN1 |
~A | Вывод очередного аргумента функцией PRINС |
~nT | Начинает вывод с колонки n. Если она уже была достигнута, выводится пробел. |
~~ | (Два знака тильды подряд) Вывод самого знака тильды |
>(format t "~~Первый параметр ~A и второй параметр ~S" 5 7) ~ 5 7 NIL |
Данная функция весьма удобна при выводе таблиц:
>(format t "~10T~%~A~10T~A~%~A~10T~A~%~A~10T~A~%" 1 2 3 4 5 6) 1 2 3 4 5 6 |
Файловый ввод и вывод несколько отличается от работы с консолью. Во-первых, для работы с файлом его надо ассоциировать с потоком и открыть. Во-вторых, по окончании работы с файлом его надо закрыть. Также различаются файлы последовательного доступа, где записи могут быть получены последовательным чтением с начала файла, и файлы прямого доступа, в которых в любой момент можно обратиться к каждой записи.
Для работы с файлами в Lisp предусмотрены следующие средства. Во-первых, определены специальные символы, указывающие на стандартное устройство ввода и стандартное устройство вывода. Эти символы, например, могут применяться в функциях PRIN1, PRINC и READ, а также FORMAT.
*STANDARD-INPUT* *STANDARD-OUTPUT* | Символы указывают на стандартные устройства ввода и вывода соответственно (т.е. клавиатуру и экран). |
Можно также связать какой-либо поток с файлом, используя специальные функции.
open | (open файл направление_обмена) Функция открывает файл для чтения или записи. Если файл необходимо открыть для чтения, в качестве направления обмена указывается:INPUT, если для записи -:OUTPUT. Для одновременного чтения и записи указывают направление обмена:IO.В качестве возвращаемого значения передаётся открытый поток, который может быть использован в других функциях. |
close | (close поток) Функция закрывает поток, открытый ранее с помощью OPEN |
with-open-file | (with-open-file (имя_потока имя_файла направление_обмена) формы) Функция открывает поток, выполняет все формы и закрывает поток, не требуя от пользователя никаких дополнительных действий. |
Отдельно следует отметить функцию, существенно облегчающую написание Lisp-программ и позволяющую создавать их во внешних редакторах и дробить на части:
load | (load имя_файла) Функция загружает и исполняет код на языке Lisp, хранящийся в указанном файле. |
Типы данных
Иерархия типов
Типы данных являются классами и подклассами используемых в программах на Lisp объектов. Как и в большинстве других языков, в Lisp различают простые и составные типы данных.
Зато важным отличием является то, что тип данных связывается не с именем символа, а с его значением, что в общем-то характерно для интерпретаторов (аналогичный механизм используется, например, в весьма популярном языке JavaScript). Присвоение переменной значения всегда является корректным, независимо от того, что это за переменная и что это за значение. Поэтому Lisp иногда называют нетипизированным (в другом варианте бестиповым) языком. Тем не менее, типы данных в нём есть, а термин обозначает лишь отсутствие жёсткой типизации.
В виду отсутствия жёсткой типизации важное значение имеет преобразование типов и проверка на принадлежность какому-либо типу.
typep | (typep объект тип) Функция возвращает Т, если объект имеет указанный тип, и NIL в противном случае. |
type-of | (typep объект) Функция возвращает имя типа, который имеет объект. |
coerce | (coerce объект тип) Функция преобразует тип объекта в указанный параметром функции COERCE. |
В качестве примера приведём следующий код:
>(type-of 'ATOM) SYMBOL >(type-of 1) FIXNUM >(type-of '(A B C)) CONS >(typep 'A 'atom) T >(typep 'A 'symbol) T >(typep 'A 'fixnum) NIL |
Таким образом, мы видим, что один и тот же символ может принадлежать к нескольким типам одновременно, т.е. между типами существуют отношения иерархии.
В таблице 2 приводится полный список всех простых типов языка Lisp, а в таблице 3 - составных (в соответствии со спецификацией ANSI Lisp):
Таблица 2
Полный перечень простых типов языка Lisp.
arithmetic-error | division-by-zero | function | real | simple-condition |
array | double-float | generic-function | restart | simple-error |
atom | echo-stream | hash-table | sequence | simple-string |
base-char | end-of-file | integer | serious-condition | simple-type-error |
base-string | error | keyword | short-float | simple-vector |
bignum | extended-char | list | signed-byte | simple-warning |
bit | file-error | logical-pathname | simple-array | single-float |
bit-vector | file-stream | long-float | simple-base-string | standard-char |
broadcast-stream | fixnum | method | simple-bit-vector | standard-class |
built-in-class | float | method-combination | style-warning | standard-generic-function |
cell-error | floating-point-inexact | nil | symbol | standard-method |
character | floating-point-invalid-operation | null | synonym-stream | standard-object |
class | floating-point-overflow | number | t | storage-condition |
compiled-function | floating-point-underflow | package | two-way-stream | stream |
complex | random-state | package-error | type-error | stream-error |
concatenated-stream | ratio | parse-error | unbound-slot | string |
Condition | rational | pathname | unbound-variable | string-stream |
Cons | reader-error | print-not-readable | undefined-function | structure-class |
control-error | readtable | program-error | unsigned-byte | structure-object |
vector | warning |
Таблица 3
Полный перечень составных типов языка Lisp.
and | double-float | member | satisfies | simple-string |
array | eql | mod | short-float | simple-vector |
base-string | float | not | signed-byte | Single-float |
bit-vector | function | or | simple-array | String |
complex | integer | rational | simple-base-string | unsigned-byte |
cons | long-float | real | simple-bit-vector | values |
vector |
В отличие от большинства языков типы языка Lisp в своей иерархии не образуют выраженного дерева, хотя обычно и говорят об иерархии типов. Во-первых, имеются два специальных типа - T и NIL, первый из которых является наиболее общим типом для всех остальных, а второй является потомком любого из определённых типов. Иными словами, каждый символ имеет тип Т, а NIL является значением, принадлежащим всем типам. Кроме того, некоторые типы имеют несколько предков. Например, тип NULL является одновременно потомком SYMBOL и LIST. Кроме того, взглянув на таблицы 2 и 3, становится понятно, что некоторые типы одновременно являются и простыми, и составными. Таким образом, иерархия достаточно запутана и в дальнейшем мы будем рассматривать типы данных отдельными фрагментами общей иерархии.
Числа
Реализация численной арифметики первоначально не была одной из основных задач при разработке языка Lisp, однако, в виду необходимости конкурировать с другими языками в области научных вычислений, Lisp был существенно расширен именно в части работы с числами. Наиболее общим типом является тип NUMBER. Этот тип обозначает любое число, будь то целое, с плавающей запятой или комплексное. Также имеются следующие типы: RATIONAL (рациональные), FLOAT (с плавающей запятой) COMPLEX (комплексные) INTEGER (целые - подмножество RATIONAL) и RATION (натуральные дроби - подмножество RATIONAL). Также рассматривается работа с "целыми числами обыкновенного диапазона" - FIXNUM, которые наиболее эффективно поддерживаются языком.
Важным моментом является задание числового значения соответствующего типа. Целые числа в языке Lisp задаются в следующей форме:
[знак]модуль[точка]
Например: 5, +5, -5 и 5. являются целыми числами. Для определения дробного числа используется черта, например, 1/5, -5/7 и т.п. Сложнее дело обстоит с плавающей запятой. Обычная в таких случаях буква E может быть заменена буквой, указывающие точность числа (зависит от реализации). Допустимы следующие модификаторы:
Таблица 4
Модификаторы типа FLOAT
Буква | Тип |
S | SHORT-FLOAT (укороченное число) |
F | SINGLE-FLOAT (одинарная точность) |
E | SINGLE-FLOAT (одинарная точность) |
D | DOUBLE-FLOAT (двойная точность) |
L | LONG-FLOAT (удлинённое число) |
Например, 5F3 является числом 5000, взятым с обычной точностью. Наконец, комплексные числа могут быть заданы с помощью модификатора #C(действительная часть, мнимая часть). Эти числа существуют не во всех модификациях Lisp.
>#C(5 5) #c(5 5) |
Для типа NUMBER определены следующие операции:
plusp minusp zerop | (plusp число) (minusp число) (zerop число) Функции возвращают Т если число является, соответственно, положительным, отрицательным и нулём. |
abs | (abs число) Функция возвращает модуль числа. |
Также определена операция унарного минуса, изменяющая знак:
>(setq X 5) >(plusp x) T >(minusp X) NIL >(minusp (- X)) T >(minusp (abs (- x))) NIL |
Отдельно следует отметить функции сравнения (табл. 5). В Lisp они могут иметь произвольное количество аргументов:
Таблица 5
Основные арифметико-логические языка:Lisp
Мнемоника | Смысл |
= | равенство |
/= | неравенство |
> | больше |
< | меньше |
>= | больше или равно |
<= | меньше или равно |
min | минимум из аргументов |
max | максимум из аргументов |
+ | Сложение |
- | Вычитание |
* | Умножение |
/ | Деление |
>(< 1 2 3) T >(< 2 1 3) NIL >(< 1 3 2) NIL >(= 1 1 1 2) NIL >(= 1 1 1 1) T >(/= 1 1 1 2) NIL >(/= 1 2 3 4) T |
Как видно, оператор < проверяет, упорядочен ли список по возрастанию, оператор = сравнивает все свои аргументы и т.п. Аналогичной особенностью обладают арифметические функции. Сложение складывает все аргументы, вычитание вычитает из первого все остальные и так далее.
Несколько отдельно стоят функции инкремента. Также как и SETF они могут изменять сам символ, а не только формировать возвращаемое значение:
incf decf | (incf переменная приращение) (decf переменная приращение) Функции соответственно увеличивают и уменьшают значение ячейки памяти на величину приращения. |
>(setq A 5) >(incf A) >A >(decf A) >A |
Очевидно, что конструкция (DECF A) эквивалентна (SETF A(- A 1)).
Рассмотрим ещё несколько математических функций:
exp expt log sqrt | (exp x) (expt x y) (log x [y]) (sqrt x) Функции работы со степенями и логарифмами: EXP вычисляет экспоненту в степени X, EXPT вычисляет X степени Y, LOG вычисляет логарифм X, если второй параметр указан, то по основанию Y, иначе по основанию E. SQRT вычисляет квадратный корень из X. |
sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh | (sin x) (cos x) (tan x) (asin x) (acos x) (atan x) (sinh x) (cosh x) (tanh x) (asinh x) (acosh x) (atanh x) Тригонометрические функции: синус, косинус, тангенс, арксинус, арккосинус, арктангенс, гиперболические синус, косинус, тангенс, арксинус, арккосинус, арктангенс. |
В качестве примера проверим правильность известной тригонометрической формулы :
>(setq x 12) >(+ (EXPT (SIN X) 2) (EXPT (COS X) 2)) 1.0 |
random | (random x) Генерирует случайные целые числа, неотрицательные и меньшие X. |
>(random 10) >(random 10) |
Для отдельных подтипов типа NUMBER предусмотрены специальные операции. Отметим следующие операции, определённые только для целых чисел:
evenp oddp gcd lcm | (evenp x) (oddp x) (gcd x [x1 x2 x3…]) (lcm x [x1 x2 x3…]) EVENP возвращает T, если число чётно, ODDP - если нечётно. GCD вычисляет наибольший общий делитель, а LCM - наименьшее общее кратное. |
>(gcd 8 14) >(lcm 8 14) |
Символы
Символ является достаточно необычной структурой данных. Во-первых, с символом может быть связано некоторое значение, с помощью использования функций SET, SETQ и SETF. Кроме того, символ может указывать на лямбда-выражение. И, наконец, символ может иметь свойства. Таким образом, символ является структурированным объектом, состоящим из нескольких элементов - имени, значения, лямбда-выражения и списка свойств, а также данные о пространстве имён, которому принадлежит этот символ.
Некоторые функции, такие как BOUNDP, FBOUNDP и SYMBOL-FUNCTION мы уже рассматривали, когда говорили о переменных и функциях. Здесь мы рассмотрим ещё несколько функций, связанных с получением значений символов.
symbol-name | (symbol-name симвыол) Возвращает имя символа в виде строки. |
>(setq x 5) >(symbol-name 'x) "X" |
Любопытной особенностью символов является возможность их связывания со специальными ячейками памяти, называемыми свойствами. Свойство символа характеризуется его именем и значением. У подобного механизма есть нечто общее с понятиями структур и записей таких языков, как С и Pascal, однако символ может иметь динамический набор свойств. Кроме того, свойства символа не связаны с его значением, независимо от изменения значения символа его свойства не меняются.
get | (get символ свойство) Возвращает ссылку на свойство символа, если такого свойства не существует, оно не создаётся, возвращается NIL. По умолчанию ссылка может использоваться для чтения его значения, однако в случае применения функции SETF становится возможно создание свойства и запись в него нового значения. |
В следующем примере мы создаём у символа y свойство property, И записываем в него значение 5. Отметим, что способ, которым это было выполнено, специфичен для Common Lisp, в других диалектах для этого используется специальная функция PUTPROP:
>(get 'y 'property) NIL >(setf (get 'y 'property) 5) >(get 'y 'property) |
У символа может быть несколько свойств, различающихся именами. Тем самым, с помощью символов можно организовывать достаточно сложные структуры данных.
symbol-plist | (symbol-plist символ) Возвращает список всех свойств и их значений для некоторого символа. |
remprop | (remprop символ имя_свойства) Удаляет присвоенное ранее символу свойство. Функция возвращает Т, если такое свойство есть, и NIL, если такого свойства нет. |
>(setf (get 'sym 'size) 'big) BIG >(setf (get 'sym 'color) 'red) RED >(symbol-plist 'sym) (SIZE BIG COLOR RED) >(remprop 'sym 'color) T >(symbol-plist 'sym) (SIZE BIG) |
Списки
Наряду с атомом, список является основной структурой данных Lisp. Как уже упоминалось, любой список состоит из ячеек, включающих в себя голову и хвост. Абстрактной списковой ячейкой является точечная пара, у которой хвост может представлять собой не только список, но и атом. Важно понимать, что точечная пара является обобщением всех списковых ячеек, и её голова и хвост сами могут быть точечными парами.
Список может быть определён рекуррентно следующим образом:
1. NIL или () является списком.
2. Структура, состоящая из точечных пар, в каждой из которых хвост является списком, сама является списком.
Таким образом, результат объединения (CONS A (B)) является списком, а (CONS A B) нет, так как B это атом, а не список. Тем не менее, обе функции в результате дадут нам точечную пару.
Списки в Lisp имеют базовый тип LIST. Два основных его подтипа это пустой список NIL и непустой список CONS. Элементом списка может быть объект произвольного типа.
Многие операции со списками были уже рассмотрены ранее, поэтому здесь мы остановимся только на некоторых, ранее не упоминавшихся:
rplaca rplacd | (rplaca список значение) (rplacd список значение) Функции изменяет значение соответственно головы и хвоста списка, указанного первым аргументом, на значение второго аргумента. |
>(setq x '(a b (c))) (A B (C)) >(rplaca x '(m n)) ((M N) B (C)) >x ((M N) B (C)) >(rplacd x 'nil) ((M n)) |
append | (append список1 список2) Функция объединяет свои аргументы-списки в один список. В отличие от LIST, аргументы APPEND считаются списками элементов, а не элементами списка. |
>(APPEND '(a (b) c) '(d (e) f)) (A (B) C D (E) F) >(LIST '(a (b) c) '(d (e) f)) ((A (B) C) (D (E) F)) |
nconc | (nconc список1 список2) Функция объединяет свои аргументы-списки в один список. В отличие от APPEND, функция разрушает структуру исходных данных - список, заданный первым аргументом, принимает значение результата NCONC. |
>(APPEND '(a (b) c) '(d (e) f)) (A (B) C D (E) F) >(nconc '(a (b) c) '(d (e) f)) (A (B) C D (E) F) >(setq arg1 '(a (b) c)) (A (B) C) >(setq arg2 '(d (e) f)) (D (E) F) >(nconc arg1 arg2) (A (B) C D (E) F) >arg1 (A (B) C D (E) F) |
Структуроразрушающие функции надо использовать с большой осторожностью, так как их вызов может неожиданным образом влиять на другие функции. Например, следующий вызов приводит к результатам, на первый взгляд достаточно странным:
>(setq x '(a)) (A) >(nconc x x) (A A A A A A A A A A...) |
Таким образом, объединяя, казалось бы, два элементарных списка, мы получили список бесконечной длины[5].
remove | (remove образец список)
Функция удаляе<
Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится... Кормораздатчик мобильный электрифицированный: схема и процесс работы устройства... Адаптации растений и животных к жизни в горах: Большое значение для жизни организмов в горах имеют степень расчленения, крутизна и экспозиционные различия склонов... Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции... © cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста. |