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

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

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

260

Глава 5

 

 

Таблица 5.2 – Коды управления печатью

Код

Назначение

~ %

Новая строка

~*

Пропуск аргумента

~A

Вывод очередного аргумента с помощью PRINC

~S

Вывод очередного аргумента с помощью PRIN1

~nT

Вывод со столбца n

~D

Десятичный вывод

~B

Двоичный вывод

~O

Восьмеричный вывод

~X

Шестнадцатеричный вывод

~F

Вывод в форме с плавающей запятой

~R

Вывод числа словами

~P

Вывод слова во множественном числе

:

Используется совместно с управляющими кодами

 

и уточняет их действие

~:P

Вывод во множественном числе, если предыдущий

 

аргумент это не 1

5.17. Функционалы

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

Например, пусть требуется написать функционал, вычисляющий сумму

n

y f (i) ,

i 1

где f (i) – арифметическая функция одного аргумента. Тогда определение функционала могло бы быть следующим:

(defun funsum (func n )

(do

(( i 1 ( + i 1 ))

 

( result 0 ( + result ( func i ) )))

 

(( = i (+ n 1 )) result )))

Здесь формальный параметр FUNC предназначен для передачи имени функции f в функционал FUNSUM. Однако вызов функционала

(funsum ’sqrt 5 )

Язык Лисп

261

 

 

завершится ошибкой: Undefined function : func(неопределена функция: func). Обусловлено это тем, что с символом FUNC не связано определение функции. FUNC в этом случае является локальной переменной, которая при вызове получает значения SQRT. Поэтому попытка интерпретации FUNC как имени функции, т.е. (func i) завершается ошибкой. Для того чтобы исправить ошибку, необходимо использовать встроенный функционал FUNCALL, который позволяет вызывать другие функции:

(funcall имя-функции &REST аргументы)

FUNCALL применяет функцию, заданную своим именем, к аргументам. Количество аргументов должно соответствовать применяемой функции.

Переопределим функционал FUNSUM с помощью FUNCALL:

(defun funsum ( func n )

(do (( i 1 ( + i 1 ))

( result 0 ( + result ( funcall func i ))) )

(( = i ( + n 1 )) result )))

Вызов функционала FUNSUM можно выполнять двумя способами:

(funsum ’sqrt 5 ) 8.382333

или

(funsum #’sqrt 5) 8.382333

Второй вызов соответствует функциональной блокировке, которая будет рассмотрена позже. Функционал FUNSUM позволяет вычислить сумму значений любой арифметической функции одного аргумента. Например,

(funsum ’exp 5 ) 233.2041

или

(funsum #’exp 5 ) 233.2041

В Лиспе также имеется встроенный функционал APPLY, который по своему действию аналогичен FUNCALL, но применяется не к отдельным аргументам, а к списку аргументов:

( APPLY имя-функции список)

Например,

(apply ’+ ’( 1 2 3 4 )) 10

или

(apply #’+ ’( 1 2 3 4 )) 10

262

Глава 5

 

 

Функционалы FUNCALL и APPLY называют применяющими или аппликативными. Функциональные параметры, передаваемые в функцию, внутри неё можно использовать только с помощью вызова применяющих функционалов FUNCALL и APPLY.

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

(MAPx имя-функции &REST список)

Здесь MAPx представляет имя одного из MAP-функционалов:

MAPCAR, MAPLIST, MAPCAN, MAPCON, MAPC, MAPL. Рассмотрим примеры вызовов этих функционалов.

MAPCAR. Этот функционал применяет функцию, заданную именем, к каждому элементу списка и в качестве результата возвращает список:

(mapcar ’sqrt ’( 1 4 9 2 3 )) (1.0 2.0 3.0 1.414214 1.732051) (mapcar ’+ ‘( 1 4 9 2 3 ) ’( 9 6 1 8 7 ) (10 10 10 10 10 )

MAPLIST. Применяется в тех случаях, когда необходимо повторить вычисления для хвостовых частей списка:

(maplist ’cons ’(a b) ’( 1 2 )) ( ((a b) 1 2 ) ((b) 2) )

Здесь функция CONS сначала применяется к спискам (A B) и (1 2), а затем к их CDR-частям , т.е. к спискам (B) и (2). Результат вычислений представляется в форме списка.

MAPCAN и MAPCON. Являются аналогом MAPCAR и MAPLIST, но объединяют результаты, получаемые от применения функции к элементам списка, в один список с помощью функции NCONC:

(mapcan ’list ’(A B C ) ’( 1 2 3)) (A 1 B 2 C 3 )

(mapcon ’list ’(A B C ) '( 1 2 3)) ((A B C ) (1 2 3) (A B) (1 2) (C) (3))

MAPC и MAPL. Эти функционалы также аналогичны MAPCAR и MAPLIST. Их обычно применяют с целью получения побочного эффекта. В качестве результата они возвращают значение второго аргумента вызова:

(mapc ’set ’(x y z ) ’( 1 4 9 )) (x y z ) x 1

Язык Лисп

263

 

 

y 4 z 9

Рассмотрим пример. Пусть требуется вычислить среднее значение элементов списка (5 3 6 7 2), затем среднее значение списка без первого элемента, т.е. (3 6 7 2) и т.д. Для решения задачи определим сначала функцию AVERAGE, вычисляющую среднее значение элементов списка, а затем применим функционал MAPLIST:

(defun average (l)

(let ( (z (length l))

(sum (apply #'+ l)) ) (float (/ sum z)) ) )

(maplist #’average ’(5 3 6 7 2)) (4.6 4.5 5.0 4.5 2.0)

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

Таблица 5.3 – Отображающие функционалы

Порядок

Способы формирования результатов

применения

Игнорирова-

Формирование

Объединение ре-

 

ние результа-

результатов в

зультатов-списков с

 

тов

список

помощью NCONC

К каждому эле-

mapc

mapcar

mapcan

менту списка

 

 

 

К хвостовым

mapl

maplist

mapcon

частям списков

 

 

 

Для работы с последовательностями в Коммон Лиспе существует ряд мощных функционалов: MAP, REMOVE, DELETE, SUBSTITUTE, FIND,

POSITION, COUNT. Функционал MAP является обобщением функции MAPCAR. Он применяет заданную функцию к каждому элементу последовательности, но дополнительно позволяет указать тип результата:

(MAP тип-результата функция &REST последовательности)

Например,

(map ’list #’- ’(1 2 3 4 )) (-1 -2 -3 -4 )

(map ’string #’(lambda (x) (if (evenp x ) #\1 #\0)) ‘(1 2 3 4 ))”0101”

264

Глава 5

 

 

Во втором примере функциональный параметр задан с помощью лямбда-выражения, которое применяется к элементам списка (1 2 3 4) и устанавливает, является ли соответствующий элемент списка четным числом. Полученный результат приводится к типу STRING.

Функционалы REMOVE, DELETE и др., упомянутые выше, имеют несколько форм – базовую форму и формы, оканчивающиеся на -IF и –IF- NOT. Указанные функционалы имеют сходные форматы вызова.

В качестве примера рассмотрим формат функционала REMOVE:

(REMOVE элемент последовательность

& KEY :from-end значение

:test значение

:test-not значение

:start значение :end значение :count значение :key значение)

Функционал удаляет элемент из последовательности и допускает различные варианты удаления с учетом значения ключевых параметров. Формы данного функционала, оканчивающиеся на IF (-IF-NOT), удаляют элемент, удовлетворяющий (не удовлетворяющий) определенному тесту:

(remove-if тест последовательность &KEY аргументы)

Здесь тест представляет функциональный параметр, а аргументы соответствуют ключевым параметрам функционала REMOVE, за исключе-

нием: TEST и TEST-NOT.

Ключевые параметры определяют следующее значения:

from-end

– задает порядок просмотра элементов последовательно-

 

сти; по умолчанию элементы просматриваются от начала

 

к концу; обратный порядок можно задать, если присво-

 

ить параметру :FROM-END значение T;

test

– определяет предикат (тест ), которому должны удовле-

 

творять обрабатываемые элементы последовательности;

 

по умолчанию применяется предикат EQL;

test-not

– определяет предикат, которому не должны удовлетво-

 

рять обрабатываемые элементы последовательности; по

 

умолчанию применяется EQL;

start

– задает начальную позицию элемента, с которого следу-

 

ет начать обработку; по умолчанию 0;

Язык Лисп

265

 

 

end

– задает конечную позицию элемента, на котором следу-

 

ет прекратить обработку.

count

– определяет количество обрабатываемых элементов по-

 

следовательности; по умолчанию обрабатываются все

 

элементы.

key

– задает функцию, которая применяется к элементу до

 

тест-проверки и которая позволяет уточнить свойство

 

проверяемого элемента.

Приведем примеры:

(remove 3 ’(1 2 3 4 3 2 1)) (1 2 4 2 1)

(remove 3 ’(1 2 3 4 3 2 1) :count 1 ) (1 2 4 3 2 1)

(remove 3 ’( 1 2 3 4 3 2 1) :count 1 :from-end t) (1 2 3 4 2 1)

(remove 3 ’(1 2 3 4 3 2 1) :start 2 :end 5) (1 2 4 2 1)

(remove 3 ’(1 2 3 4 3 2 1) :test #’> ) (3 4 3)

(remove ’A ’((A B ) (C D) (A E)) :test #’eq :key #’car) ((C D))

(remove-if #‘oddp ’(1 2 3 4 3 2 1)) (2 4 2) (remove-if-not #’evenp ’(1 2 3 4 3 2 1)) (2 4 2)

5.18. Макролитеры ( макросы чтения )

Чтение литеры, входящей в алфавит языка, не требует никаких специальных действий. В то же время чтение некоторых особых литер: круглых скобок, точки с запятой, двойных кавычек – приводит к выполнению специальных действий. Литеры, требующие специальных действий, называются макролитерами или макросами чтения. Соответствие между различными литерами и действиями задается таблицей чтения. Могут существовать различные таблицы чтения. Однако в каждый момент времени рабочая таблица одна. Она определяется значением динамической переменной *readtable*. Таблица чтения доступна пользователю, и он, при желании, может вносить в нее новые интерпретации литер и, тем самым, менять синтаксис языка.

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

(set-macro-character литера функция )

Например, действия, выполняемые при чтении литеры “ ’ ”(quote), блокирующей вычисления, могут быть внесены в таблицу чтения следующим образом:

266

Глава 5

 

 

(set-macro-character # \’

#’(lambda (stream char) (list (quote quote)(read stream))))

К стандартным макролитерам чтения относятся следующие:

макролитеры:: = ( | ) | ’ | ; | ” | # | ‘ | ,

Здесь литеры “ ( “ и “ ) “ начинают и заканчивают ввод списка или точечной пары, литера “ ’ “ блокирует вычисления, литера “ ; “ начинает комментарий, литера “ ” ” начинает и заканчивает ввод строки.

Литера “ # ” называется диспетчером. Ее значение зависит от дополнительной литеры, следующей после знака # . Например, #’ обозначает функциональное замыкание, #\ - используется для обозначения стандартных литер, #C - используется для обозначения комплексных чисел и т. д. Имеется более двадцати возможных комбинаций литеры диспетчера с другими литерами. Для того чтобы внести в таблицу чтения последовательность, состоящую из литеры диспетчера и дополнительной литеры, и связать с ней необходимую функцию обработки, применяется функция SET-

DISPATCH-MACRO-CHARACTER.

Литера " ‘ " называется обратной блокировкой вычислений, она дей-

ствует аналогично обычной блокировке. Но внутри обратно блокированного выражения можно локально отменить блокировку вычислений. Локальная отмена блокировки выполняется с помощью макролитеры “ , “. Для этого литера “ , “ записывается непосредственно перед выражением, для которого необходимо отменить блокировку:

(setf v ’dog w ’cat) CAT

‘(,v and ,w) (DOG AND CAT)

Кроме запятой, для отмены блокировки можно использовать комбинацию знаков “ ,@ “:

(setf x ’(dog and cat)) (DOG AND CAT) ‘(i see ,@x) (I SEE DOG AND CAT)

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

Рассмотренные макросы чтения позволяют расширять и изменять синтаксис языка Лисп. Механизм обратной блокировки облегчает написание макросов, рассматриваемых ниже.

Язык Лисп

267

 

 

5.19. Замыкания

При рассмотрении функционалов было отмечено, что задание функционального аргумента в вызове функционала требует блокировки вычисления такого аргумента, так как он передает имя функции. Данная блокировка может быть сделана либо с помощью формы QUOTE, либо с помощью последовательности знаков #’. Последняя блокировка указывает на то, что записанная после нее форма должна рассматриваться как функция. Аналогично тому, как запись ’(форма) эквивалентна (quote (форма)), так и запись #(форма) эквивалентна записи (function форма). Специальную форму FUNCTION называют функциональной блокировкой.

Форма, указанная в вызове (function форма), может быть либо символом, либо лямбда-выражением. Если форма представлена символом, то значение, возвращаемое таким вызовом, является именем функции, код которой и будет применяться к соответствующим аргументам с помощью функции FUNCALL или APPLY ( см. примеры в §5.17). Если форма представлена лямбда-выражением, то генерируется так называемое лексическое замыкание (lexical closure). Суть его состоит в том, что к определению функции, которое заданно лямбда-выражением, добавляются связи свободных переменных, входящих в лямбда-выражение, т.е. возникает замыкание функции и некоторого контекста ее определения. Замыкания можно использовать в качестве функционального аргумента. При вызове замыкания значения свободных переменных лямбда-выражения берутся из контекста создания замыкания.

Рассмотрим пример. Определим функцию ADDER-GENERATOR, которая генерирует некоторое число, превышающее на величину Y значение, запомненное в момент лексического замыкания:

(defun adder-generator (x) #’(lambda (y) ( + x y )) ) ADDER-GENERATOR

Замыкание можно запоминать, присваивая его какому-нибудь символу (переменной):

(setf add-7 (adder-generator 7)) #<LEXICAL-CLOSURE…>

В данном случае замыкание запоминается как значение символа ADD-7. Можно также запомнить замыкание как функцию, которая связана с символом:

(setf (symbol-function ’add-3 ) (adder-generator 3 )) #<LEXICAL-CLOSURE …>

В случае первого присваивания свободная переменная X лямбдавыражения получила значение 7, а в случае второго присваивания – 3.

268

Глава 5

 

 

Кроме того, с символом add-3 связано определение функции. Поэтому вызовы данных замыканий выполняются по-разному:

(funcall add-7 2) 9

(add-3 2) 5

Замыкания хорошо подходят для написания различных программгенераторов. Рассмотрим пример:

(defun even-odd-generator (seed) (list

#’(lambda( )

(setf seed (cond (( evenp seed) ( + seed 2))

(t ( + seed 1 )))))

#’(lambda ( )

(setf seed (cond (( oddp seed) ( + seed 2)) (t ( + seed 1 )))))))

Функция EVEN-ODD-GENERATOR позволяет генерировать четные и нечетные числа. Она включает в себя два замыкания. Запомним эти замыкания в переменной FUNS:

(setf funs (even-odd-generator 0 )) #<LEXICALLCLOSE…>

Теперь можно генерировать четные и нечетные числа с помощью следующих вызовов:

(funcall (car funs)) 2

(funcall (car funs)) 4 (funcall (cadr funs)) 5

(funcall (cadr funs)) 7

5.20. Структуры

Ассоциативные списки и списки свойств позволяют объединять элементы данных в комплексы для их более адекватного представления в памяти ЭВМ. При работе с комплексами данных выделяют три основные операции: создание (конструирование) комплекса данных; чтение значений элементов данных комплекса; изменение значений элементов данных комплекса.

Применительно к ассоциативным спискам указанные операции выполняются с помощью функций PAIRLIS, ASSOC и формы SETF. В случае списков свойств указанные действия выполняются с помощью функции GET и обобщенного присваивания SETF. Достоинством ассоциативных

Язык Лисп

269

 

 

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

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

(defstruct имя {описания_слотов}*)

Данный вызов определяет составной тип с заданным именем и набором слотов (полей). Конкретизируем данное определение на примере:

(defstruct sportsman

 

(name nil)

; имя

(sport nil)

; вид спорта

(data nil))

; дата рождения

SPORTSMAN

Вэтом определении описывается абстрактная структура SPORTSMAN, которая включает в себя три слота: имя спортсмена (NAME), вид спорта (SPORT), дату рождения (DATA). Каждый слот (поле) структуры инициализируется значением NIL. При необходимости можно инициализировать слоты структуры иными значениями, а также указывать типы их значений.

Вызов макроса DEFSTRUCT приводит к выполнению ряда побочных действий.

1. Генерируется функция-конструктор, имя которой образуется из слова MAKE- и имени структуры. Например, MAKE-SPORTSMAN. Данная функция используется для создания конкретного экземпляра структуры

SPORTSMAN:

(setf x (make-sportsman))

#S(SPORTSMAN :NAME NIL :SPORT NIL :DATA NIL)

Здесь знак “#S” обозначает структуру. Слотам конкретного экземпляра структуры можно присвоить значения с помощью ключевых параметров в вызове MAKE-SPORTSMAN . Ключевой параметр представляет собой имя соответствующего слота структуры, перед которым стоит двоеточие:

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