Добавил:
natribu.org Все что нашел в интернете скидываю сюда Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Точно Не проект 2 / Не books / Источник_1

.pdf
Скачиваний:
9
Добавлен:
01.02.2024
Размер:
20.67 Mб
Скачать

220

Глава 5

 

 

 

 

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

5.5. Основные функции обработки списков

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

К операциям выделения отдельных частей списков и создания из них новых структур сводятся многие алгоритмы обработки символьной информации. Основными функциями, на основе которых выполняются указанные действия, являются встроенные функции CAR, CDR и CONS. Ниже рассматриваются также и другие функции, облегчающие работу со списками и имеющиеся в языке Коммон Лисп. Эти функции определяются на основе функций CAR, CDR, CONS. Поскольку список – частный случай точечной пары, то ниже рассматриваются функции, аргументы которых заданы точечными парами.

Для доступа к первому элементу точечной пары или списка применяется функция CAR. Например,

(car ’(a . b)) А (car ’(a b)) А

Здесь апостроф, обозначает вызов специальной формы QUOTE , которая блокирует вычисление значения s-выражения (а . b) или (а b). Если этого не сделать, то Лисп-интерпретатор (EVAL) предпримет попытку вычислить эти s-выражения. В соответствии с правилами работы интерпретатора, первый элемент s-выражения воспринимается как имя функции. Однако, на самом деле, функции с именем А нет. Поэтому возникает ошибка: UNDEFINED-FUNCTION (неопределенная функция). Применение функции QUOTE сообщает Лисп-системе, что аргументы функции CAR не подлежат вычислению и представляют самих себя. Необходимость блокировки вычислений возникает всякий раз, когда рассматриваются символьные данные.

Для выделения второго элемента точечной пары применяется функция CDR. Например,

(cdr ’(a . b)) В (cdr ’(a b)) (В)

(caar x),(cadr x),(cdar x),(cddr x),

Язык Лисп

221

В первом случае возвращается атом В, а во втором случае – список, содержащий атом В. Этот результат объясняется тем, что список (А В) в точечной записи имеет вид (А . (В . NIL)). Функция CDR возвращает второй элемент точечной пары ( В . NIL), т.е. (B).

Функция CAR и CDR определены только для точечных пар. Для аргументов атомов результаты функций CAR и CDR неопределены. Поэтому при попытке вызова (car ’a) формируется сообщение об ошибке.

Для выделения произвольных элементов списка используются ком-

позиции функций CAR и CDR. Например,

(car (car ’((a b) (b c)))) A (car (cdr (cdr ’(1 2 3)))) 3

Композиции функций CAR и CDR встречаются довольно часто, поэтому для них введены специальные имена:

(car (car x)) (car (cdr x)) (cdr (car x)) (cdr (cdr x))

(cdr (cdr (cdr (cdr x )))) (cddddr x).

В Коммон Лиспе для выделения отдельных элементов списков можно использовать более удобные имена функций, отражающие суть выполняемой операции. Например:

REST – эквивалентна CDR, возвращает хвост вписка; FIRST– эквивалента CAR, возвращает первый элемент вписка;

SECOND – эквивалентна CADR, возвращает второй элемент списка; THIRD - эквивалентна CADDR, возвращает третий элемент списка и т.д.;

TENTH – возвращает десятый элемент списка. Имена указанных функций соответствуют порядковым числительным английского языка.

Для создания точечной пары из s-выражений в Лиспе применяется функция CONS (конструктор):

(cons ’a ’b) (A . B)

Конструирование списков с помощью функции CONS осуществляется последовательными вызовами:

(cons ( ’x (cons ’y (cons ’z NIL)))) (X Y Z)

222

Глава 5

 

 

 

 

Здесь сначала строится точечная пара (Z . NIL), затем точечная пара

(Y . (Z . NIL)), а в конце – точечная пара (X .(Y . (Z . NIL))), которая и представляет список (X Y Z). Таким образом, функция CONS включает очередной элемент в начало списка, представленного ее вторым аргументом. Конструирование списков с помощью CONS не очень наглядно. В Лиспе имеется функция LIST с произвольным числом аргументов, которая упрощает задачу создания списков:

(list 1 ’b ’c) (1 B C)

Списки можно также создавать из других списков путем их объединения. Для этого применяется функция APPEND. Например,

(append ’(1 2 a) ’(b 4 5)) (1 2 A B 4 5)

В Лиспе имеется ряд других полезных функций для обработки списков: LAST – выделяет последний элемент списка (результат – список); NTH – выделяет n-й элемент списка;

SUBST – выполняет замену элементов в списке; BUTLAST – выделяет список без последних элементов; MEMBER – проверяет принадлежность элемента списку; LENGTH – вычисляет длину списка;

REVERSE – выполняет обращение списка;

ADJOIN – добавляет элемент в множество, представленное списком; REMOVE– удаляет элемент из списка.

Примеры:

(last ’(a b c d))

(D)

(nth 3 ’(a b c d))

D

; счет начинается с нуля

(subst ’a ’b ’(a b c))

(A

A C)

(butlast ’(a b c d) 2)

(A

B)

(member ’c ’(a b c d))

(C

D)

(length ’(a b (2 3)))

3

 

(reverse ’(1 2 e 4))

(4

E 2 1)

(adjoin ’a ’(a b c d))

(A B C D)

(remove ’a ’(a b a b a b))

(B B B)

Отметим, что все рассмотренные функции никоим образом не воздействуют на свои аргументы. Вычисление значений этих функций не приводит к побочным эффектам, т.е. следствием вызова функции является значение, возвращаемое в точку вызова.

Язык Лисп

223

5.6. Присваивание значений

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

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

Функция SET имеет два аргумента. Первый аргумент представляется формой, задающей символ, а второй – формой, определяющей значение символа, например:

(set ’x ’(a b c)) (A B C) (set (car x) ’(d e)) (D E)

Здесь символу Х присваивается значение (А В С), а символу А значение – ( D E).

Символу можно также присвоить значение с помощью вызова специальной формы SETQ или макроса SETF. Формат вызова специальной формы SETQ:

(setq {символ форма }*)

В отличие от функции SET, форма SETQ блокирует вычисление значений символов. Например,

(setq

x ’(a

b c))

(A B C)

(setq

y 12

z (+ y

10)) 25

Присваивание значений символам в форме SETQ выполняется последовательно слева направо, т.е. в момент присваивания значения символу Z, символ Y уже получил значение 12. Результатом вызова SETQ является последнее присвоенное значение или NIL, если аргументы формы SETQ не были заданы.

Макрос SETF (set field) является обобщенной формой, используемой для изменения значений различных объектов. Формат вызова SETF:

224

Глава 5

 

 

 

 

(setf {место форма }*)

Здесь место – вычисляемое выражение, позволяющее определить лисповскую ячейку памяти, значение которой необходимо изменить.

Примеры:

(setf a 8.8 d 2.0) 2.0 (setq x ’( a b c)) ( A B C)

(setf (car x ) 2 (cadr x ) 3) 3

x(2 3 C)

Впервом примере символ А получает значение 8,8 , а символ d 2,0. Результатом вызова является последнее присвоенное значение – 2,0. Если принять, что значением символа Х является список (А В С), то второй вызов макроса SETF меняет в этом списке вхождение А на 2, а В на 3. Иными словами, после вызова SETF символ Х будет иметь значение (2 3 С). Если при вызове SETF аргументы не задавать, то результатом вызова будет NIL. Обработка пар аргументов макроса SETF выполняется последовательно слева направо. Макрос SETF позволяет изменять не только значение символа, но и другие его атрибуты.

5.7. Предикаты

Для обработки данных необходимы различные функции, позволяющие выяснять те или иные свойства данных. Так, выполняя арифметические функции, следует убедиться в том, что аргументы – числа, а в случае применения функций CAR и CDR в том, что аргументы представлены точечными парами. Функции, выполняющие такую проверку, возвращают логические значения и называются предикатами. В Лиспе имеется большой набор предикатов. Рассмотрим основные встроенные предикаты Лиспсистем.

Функция EQ сравнивает значения двух своих аргументов. При этом сравнение выполняется не на логическом уровне, а на физическом, т.е. выясняется, представлены ли аргументы в памяти ЭВМ одной и той же физической структурой данных. Функция EQ возвращает значение Т, если ее

аргументы – символы, и в памяти ЭВМ они представлены одной и той же структурой. Сравните следующие вызовы:

(setq x ’(a b c)) (A B C) (setq y ’(a b c)) (A B C) (eq x y) NIL

и

(setq x ’(a b c)) (A B C)

Язык Лисп

225

 

 

setq

y x ) (A B C)

(eq

x y ) Т

В первом случае, несмотря на логическое равенство значений символов Х и У, функция EQ возвращает NIL. Это объясняется тем, что символы Х и У представляются в памяти ЭВМ двумя разными структурами. Так как функция EQ требует, чтобы ее аргументы были символами, то при сравнении чисел могут получаться неожиданные результаты. Например,

(eq 5.0 5.0) NIL

В Лиспе имеется функция EQL, которая аналогична EQ, но дополнительно позволяет сравнивать числовые значения одинаковых типов. Сравнение чисел разных типов выполняется с помощью предиката “=”. Например,

(eql

5.0

5.0) Т

(= 5

5.0

0.5e1) Т

Сравнение объектов Лисп-программы на уровне абстрактных структур выполняется с помощью предиката EQUAL. Данный предикат обобщает предикат EQL и может сравнивать символы, однотипные числа, строки, списки:

(equal

’a ’a) Т

(equal ’(a b c) ’(a b c)) Т

(equal

“lisp”

”lisp” ) Т

(equal

5.0

5.0) Т

Однако

 

5) NIL

(equal

5.0

Наиболее общее логическое равенство лисповских объектов устанавливается с помощью предиката EQUALP. Дополнительно к возможностям EQUAL данный предикат устанавливает логическое равенство разнотипных чисел.

Предикат NULL позволяет установить, представляет ли его аргумент пустой список:

(null (cdr ’( a ))) Т (null nil) Т

(null T) NIL

Из двух последних вызовов предиката NULL следует, что применительно к логическим аргументам он выполняет функцию логического отрицания.

226

Глава 5

 

 

В Лиспе имеется большой выбор предикатов, классифицирующих типы значений: ATOM, NUMBER, INTEGERP, FLOATP, RATIONALP, SYMBOLP,

LISTP и др. Предикат АТОМ проверяет, представляет ли его аргумент атом:

(atom ’a) Т

(atom nil) Т

Предикат NUMBERP устанавливает, является ли его аргумент чис-

лом:

(number 5.0) Т (number ’a) NIL

Предикаты INTEGERP, FLOATP, RATIONALP позволяют выяснить, относится ли значение аргумента к целым, вещественным или рациональным числам, соответственно.

Предикаты SYMBOLP, CONSP, LISTP позволяют из множества различных значений выделять символы, точечные пары и списки:

(setq x ’(a b c)) (A B C) (symbolp x) NIL

(consp x) Т (listp x) Т (consp ’( )) NIL (listp ’( )) Т

Для проверки свойств числовых значений в Лиспе используются предикаты: ZEROP (равенство нулю), ODDP (нечетность), EVENP (четность), MINUSP (отрицательность).

Рассмотренные предикаты обычно применяются при построении тест-форм условных выражений.

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

Сматематической точки зрения функция f представляет отображение множества Х на множество Y:

f: Х Y.

Множество Х – задает область определения функции, а множество Y

– область значений функции. Говорят, что f – это функция типа X Y . При этом каждому элементу x X соответствует единственный

элемент y Y. Это обычно записывают в хорошо известной форме y= f(x).

Язык Лисп

227

 

 

Таким образом, функция f – это множество пар (x, y), связанных свойством y=f(x), т.е.

f x, y X Y: y f (x) ,

где – прямое произведение множеств Х и Y.

В общем случае функция может задавать отображение многих мно-

жеств Х1, Х2, … , Хn

на множество Y. Такая функция относится к типу

X1 X2 .... Xn yn

и записывается в виде

f : X1 X2 ... Xn Y .

Приведенная запись означает, что функция имеет n-аргументов. Причем тип первого аргумента функции задается множеством Х1, второго

– множеством Х2 и т.д.

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

В языке Лисп для этого применяется лямбда-выражение:

(lambda ( {переменная}*){форма}*)

Здесь конструкция ({переменная}*) называется лямбда-списком и представляет собой список формальных параметров, которые соответствуют аргументам функции. Последовательность форм в лямбда выражении определяет правило формирования значения функции и называется телом функции.

Пусть требуется определить функцию, вычисляющую х5. Тогда лямбда-выражение будет иметь вид:

(lambda (x) ( * x x x x x))

Чтобы задать функцию, строящую список из двух аргументов х и y, можно использовать выражение

(lambda (x y) (cons x (cons y nil )))

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

228

Глава 5

 

 

(( lambda ( x ) ( * x x x x x)) 4 ) 1024

(( lambda ( x y) (cons x (cons y nil ))) ’a ’b) (А В)

Такие вызовы называются лямбда-вызовами. В первом примере формальному параметру Х лямбда-списка присваивается фактическое значение 4, а затем вычисляется тело лямбда-выражения. Во втором примере формальным параметрам Х и У назначаются значения фактических параметров, заданных атомами А и В, из которых строится список (А В). По окончании вычислений связи между формальными параметрами и значениями фактических параметров разрушаются.

Проводить вычисление с помощью лямбда-вызовов неудобно, так как приходится каждый раз при очередном вызове заново переписывать тело функции. Проблема решается, если с лямбда-выражением связать некоторое имя (символ), которое будет выступать в роли имени функции, определяемой лямбда-выражением. Для связи имени и лямбда-выражения в Лиспе применяется форма DEFUN:

( defun имя лямбда-выражение )

При этом для упрощения записи в лямбда-выражении опускается слово LAMBDA и скобки. В итоге получаем:

(defun имя лямбда-список {форма}*)

Результатом вызова формы DEFUN является имя определенной функции. Например:

(defun

stepen 5 ( x ) ( * x x x x x)) STEPEN 5

(defun

spisok ( x y ) (cons x (cons y nil ))) SPISOK

Теперь указанные выше лямбда-вызовы можно заменить следующи-

ми вызовами:

(stepen 5

4) 1024

 

 

(spisok ’a

’b) (А В)

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

При задании формальных параметров в форме DEFUN или в лямбдасписке языка Коммон Лисп выделяют:

-обязательные параметры;

-необязательные параметры (optional);

-остаточные параметры, связываемые с хвостом списка параметров

(rest);

-ключевые параметры (key);

Язык Лисп

229

 

 

- вспомогательные переменные (auxiliary).

Рассмотренные выше примеры определения функций содержали только обязательные параметры. Если необходимо в списке параметров указать параметры, отличные от обязательных, то их отмечают соответствующими ключевыми словами: &OPTIONAL, &KEY, &REST, &AUX. Указанные средства существенно расширяют возможности управления передачей параметров в функции.

Необязательные параметры при вызове функции можно не задавать. В этом случае они получают значение NIL или значения по умолчанию, определяемые формой, связанной с таким параметром. В списке формальных параметров необязательные параметры записываются после слова &OPTIONAL. Например,

(defun fn (x &optional (y (+ x 5))) (+ x y ) )

Здесь X – обязательный параметр, а Y – необязательный. Если параметр у не будет задан при вызове функции, то он получит значение по умолчанию, равное X+5:

(fn 2) 9 (fn 2 3 ) 5

Параметр, указанный после ключевого слова &REST, является остаточным и соответствует списку, составленному из тех значений фактических параметров, которым не были назначены формальные параметры. Остаточный параметр является необязательным. В следующем примере Z– остаточный параметр. Функция PRINT_ARG печатает значения своих аргументов:

(defun print_arg ( x &optional y &rest z) ( print x)

( print y) z )

(print_arg 1 ) 1

NIL

NIL

(print_arg 1 2 3 4 5 ) 1

2 (3 4 5 )

Введение остаточных параметров позволяет легко определять функции с произвольным количеством аргументов.

Ключевые параметры при определении функции следуют после слова &KEY, а при вызове функции указываются с помощью имени параметра,

Соседние файлы в папке Не books