Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие Функциональное и логическое программирование.docx
Скачиваний:
4
Добавлен:
01.07.2025
Размер:
309.57 Кб
Скачать

Макросы

Обратная блокировка. Обычная блокировка не позволяет вычислять выражения:

'(a b c) => (a b c)

Иногда возникает необходимость вычислить только часть выражения. Если

(setq b '(x y z)) => (x y z)

и мы запишем

`( a ,b c) ,

то b - будет вычислено и мы получим (a (x y z) c). Обратная верхняя запятая (`) обозначает блокировку, которая может частично сниматься запятой. Обратная блокировка может использоваться при печати:

(setq n 'john)

(setq m 'Robert)

(print `(boys ,n and ,m))

=> (boys john and roberts)

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

Рассмотрим, например, функцию blancs, которая производит n переводов каретки (пропускает n линий):

(defun blancs (n)(do ((count n ( - count 1)))(( zerop count) nil)(terpri))) => blancs

(blancs 4) - пропустит четыре строки.

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

(do-times '(terpri) 4)

Или

(do-times '(print 'hello) 3) - напечатает hello три раза.

Можно через eval определить:

(defun do-times (operation n)(do ((count n ( - count 1)))(( zerop count) nil)(eval operation))) => do-times

Это можно сделать также через специальную форму defmacro:

(defmacro do-times (operation n)` (do ((count ,n ( - count 1)))(( zerop count) nil) ,operation)).

Как видно, форма макро похожа на определение функции, но есть отличия:

1. Аргументы макро не вычисляются перед их использованием. Тогда обращение записывается: (do-times (print 'hello) 3)

2. Когда макро вызывается, Лисп сначала вычисляет тело макро, а затем выполняет получившееся выражение. Например, после обращения (do-times (print 'hello) 3) получим(do ((count 3 ( - count 1)))(( zerop count) nil)(print 'hello)). Этот список потом вычисляется.

Таким образом, при вызове макро сначала вычисляется тело (этап называется расширением) и формируется выражение.

На втором этапе вычисляется полученное выражение, и полученное значение возвращается как результат.

Вызывая macro с разными аргументами, получим разные результаты. Если мы вызываем:

(do-times (print count) 10),

после вычисления тела получим:

(do ((count 10 ( - count 1)))(( zerop count) nil)(print count)).

Печатаются числа от 10 до 1.Можно определить функцию обратной печати чисел:

(defun print-number (n)(do-times (print count) n))( print-number 5)

Разработка макро. При разработке макро необходимо выполнить три шага:

      1. Написать пример функции, которую должен формировать макро,

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

      3. Определить макро, который реализует этот вызов.

Пример. Необходимо определить макро term-search, который будет просматривать список и выделять первый элемент, удовлетворяющий заданному условию.

Шаг1. Сформулируем пример. Запишем тело для поиска чисел:

(setq l '(s d 3 d)) (setq item 5)

(do (( tail l (cdr tail)))

((null tail) nil)

( cond ((numberp (car tail)) (return (car tail)))))

~~~~~~~

Шаг2. Выделяем общие части список lis - l и предикат predicate - numberp.

Шаг3.Формируем макро

(defmacro term-search ( predicate lis)

` (do (( tail , lis (cdr tail)))

((null tail) nil)

(cond ((,predicate (car tail)) (return (car tail))))))