Имена и контексты
Реализация языка программирования всегда сопровождается некоторым уточнением границ, в которых применяются общеизвестные понятия. Цель уточнения - удобство программирования и повышение эффективности программ. Рассмотрим отдельные решения, уточненные при реализации ряда Лисп-систем, на небольшом примере моделирования работы с множествами.
Задача: Пусть множества представлены с помощью списков. Для начала рассмотрим простые множества, элементами которых могут быть только атомы. Надо реализовать объединение ( 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)
