Имена и контексты
Реализация языка программирования всегда сопровождается некоторым уточнением границ, в которых применяются общеизвестные понятия. Цель уточнения - удобство программирования и повышение эффективности программ. Рассмотрим отдельные решения, уточненные при реализации ряда Лисп-систем, на небольшом примере моделирования работы с множествами.
Задача: Пусть множества представлены с помощью списков. Для начала рассмотрим простые множества, элементами которых могут быть только атомы. Надо реализовать объединение ( UNION ) и пересечение ( INTERSECTION ) множеств.
Предварительный анализ задачи:
Функции UNION и INTERSECTION применяют к множествам, каждое множество представлено в виде списка атомов. Заметим, что обе функции рекурсивны и используют вспомогательную функцию, выясняющую входит ли атом в список (MEMBER).
Работу этих функций можно выразить следующим образом:
MEMBER – это функция двух аргументов, первый аргумент "А" - атом, а второй аргумент – список "Х". Функция вырабатывает значение "Т", если "А" входит в список "Х".
Определение тела функции состоит из трех ветвей:
-
Если второй аргумент – пустой список,
то значение функции Nil, т.е. атом в списке не найден.
-
Иначе если атом "А" совпадает с "головой" второго аргумента,
то значение функции T, т.е. атом имеется в списке.
- Иначе продолжаем поиск в "хвосте" списке, т.е. рекурсивно применяем исходную функцию к редуцированному второму аргументу.
алг member ( атом a, список x) арг a, x нач если пусто (a) то знач := Nil инес равно (a, голова (x) ) то знач := T иначе знач := member (a, хвост (x)) кон
UNION – это функция двух аргументов, оба аргумента "X" и "Y" - списки, представляющие множества. Функция вырабатывает новый список, в который входят все атомы из списков "Х" и "Y".
Определение тела функции состоит из трех ветвей:
-
Если первый аргумент – пустой список,
то значением является второй аргумент, т.е. можно ничего не строить.
-
Иначе если "голова" первого аргумента входит во второй аргумент,
то достаточно объединить хвост первого аргумента со вторым аргументом, т.е. рекурсивно применяем исходную функцию, редуцируя первый аргумент.
- Иначе "голову" первого аргумента присоединяем к результату объединения редуцированного первого аргумента со вторым аргументом.
алг UNION (список x,y) арг x, y нач если пусто (x) то знач := y инес member ( голова (x), y ) то знач := UNION (хвост (x), y) иначе знач := cons (голова (x), UNION (хвост (x), y)) кон
INTERSECTION – это функция двух аргументов, оба аргумента "X" и "Y" - списки, представляющие множества. Функция вырабатывает новый список, в который входят атомы списка "Х", входящие в список "Y".
Определение тела функции состоит из трех ветвей:
-
Если первый аргумент – пустой список,
то и пересечение - пустой список.
-
Иначе если "голова" первого аргумента входит во второй аргумент,
то "голову" первого аргумента присоединяем к результату пересечения редуцированного первого аргумента со вторым аргументом.
- Иначе применяем пересечение к редуцированному первому аргументу со вторым аргументом.
алг INTERSECTION (список x,y) арг x, y нач если пусто (x) то знач := Nil инес member ( голова (x), y ) то знач := cons (голова (x), INTERSECTION (хвост (x), y)) иначе знач := INTERSECTION (хвост (x), y) кон
Определяя эти функции на Лиспе, мы используем специальную псевдо-функцию DEFUN. Программа выглядит так:
(DEFUN MEMBER (A X) ;определение проверки входит ли атом в список (COND ((NULL X) Nil) ((EQ A (CAR X)) T) (T (MEMBER A (CDR X)) ) ) ) (DEFUN UNION (X Y) ;определение объединения двух множеств (COND ((NULL X) Y) ((MEMBER (CAR X) Y) (UNION (CDR X) Y) ) (T (CONS (CAR X) (UNION (CDR X) Y))) )) ) )) (DEFUN INTERSECTION (X Y) ;определение пересечения двух множеств (COND ((NULL X) NIL) ((MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION (CDR X) Y)) ) (T (INTERSECTION (CDR X) Y)) )) (INTERSECTION '(A1 A2 A3) '(Al A3 A5)) ;тест на пересечение двух множеств (UNION '(X Y Z) '(U V W X)) ;тест на объединение двух множеств
Эта программа предлагает Лисп-системе вычислить пять различных форм. Первые три формы сводятся к применению псевдо-функции DEFUN. Значение четвертой формы - (A1 A3). Значение пятой формы - (Y Z C B D X). Анализ пути, по которому выполняется рекурсия, показывает, почему элементы множества появляются именно в таком порядке.
Псевдо-функция - это функция, которая выполняется ради ее воздействия на систему, тогда как обычная функция - ради ее значения. DEFUN заставляет функции стать определенными и допустимыми в системе равноправно со встроенными функциями. Ее значение - имя определяемой функции, в данном случае - MEMBER, UNION, INTERSECTION. Можно сказать более точно, что полная область значения псевдо-функции DEFUN включает в себя некоторые доступные ей части системы, обеспечивающие хранение информации о функциональных объектах, а формальное ее значение – атом, символизирующий определение функции.
В этом примере продемонстрировано несколько элементарных правил написания функциональных программ, выбранных при реализации интерпретатора Лисп 1.5 в дополнение к идеализированным правилам, сформулированным в строгой теории Лиспа, которая описана в предыдущем разделе.
- Программа состоит из последовательности вычисляемых форм. Если форма список, то ее первый элемент интерпретируется как функция. Остальные элементы списка – аргументы для этой функции. Они вычисляются с помощью EVAL, а функция применяется к ним с помощью APPLY и полученное значение выводится как результат программы.
- Нет особого формата для записи программ. Границы строк игнорируются. Формат программы, включая идентификацию, выбран просто для удобства чтения.
- Любое число пробелов и концов строк можно разместить в любой точке программы, но не внутри атома.
- Не используются (QUOTE T) и (QUOTE NIL). Вместо них используется T и NIL, что влечет соответствующее изменение определения EVAL.
- Атомы должны начинаться с букв, чтобы легко отличаться от чисел.
- Точечная нотация может быть привлечена наряду со списочной записью. Любое число пробелов перед или после точки, кроме одного, будет игнорироваться (один пробел обязательно нужен).
- Точечные пары могут появляться как элементы списка, и списки могут быть элементами точечных пар.
Например:
((A . B) X (C . (E F D))) - есть допустимое S-выражение.
Оно может быть записано как
((A . B) . ( X . ((C . (E . ( F . (D . Nil))) ) . Nil)))
или
((A . B) X (C E F D))
- Форма типа (A B C . D) есть сокращение для (A . ( B . ( C . D) )). Любая другая расстановка запятых или точек на одном уровне есть ошибка, например, (A. B C) или (A B . C D) не соответствуют никакой структуре данных. (Реализационное расширение списочной записи. " . D " здесь означает, что вместо Nil, по умолчанию завершающего список, в данной структуре размещен атом " D ")
- Набор основных функций обеспечен системой. Другие функции могут быть введены программистом. Любая функция может использоваться в определении другой функции с учетом иерархии построений.
При наборе форм в диалоге интерпретатор сам напечатает результаты, а при загрузке программы их файла надо позаботиться о выводе результатов программы с помощью псевдо-функции PRINT.
(PRINT (INTERSECTION '(A1 A2 A3) '(Al A3 A5)) ) (PRINT (UNION '(X Y Z) '(U V W X)) ) (PRINT (UNION (READ) '(1 2 3 4)) ) ; объединение вводимого списка со списком '(1 2 3 4)