Новосибирский Государственный Университет
Опубликован: 05.02.2007 | Доступ: свободный | Студентов: 2252 / 437 | Оценка: 4.30 / 4.23 | Длительность: 10:15:00
Лекция 6:

Интерпретатор

< Лекция 5 || Лекция 6: 12 || Лекция 7 >
Аннотация: Теперь рассмотрим определение операционной семантики Лиспа в виде универсальной функции, задающей правила вычисления форм и применения функций к аргументам. Проанализируем требования к определению семантики, удобно сопоставимой с синтаксической сводкой правил языка. Дадим лаконичное определение универсальной функции EVAL, вычисляющей произвольное выражение языка, и APPLY, применяющей функции языка к их аргументам. Отметим специфику предикатов и истинности, принятой в языке Лисп.

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

Определим универсальную функцию eval от аргумента - выражения, являющегося произвольной вычислимой формой языка Лисп.

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

  • Атомарное выражение обычно понимается как переменная. Для него следует найти связанное с ним значение. Например, могут быть переменные вида " x ", " elem ", смысл которых зависит от контекста, в котором они вычисляются.
  • Константы, представленные как аргументы функции QUOTE, можно просто извлечь из списка ее аргументов. Например, значением константы (QUOTE T) является атом T, обычно символизирующий значение "истина".
  • Условное выражение требует специального алгоритма для поиска истинных предикатов и выбора нужной ветви. Например, интерпретация условного выражения
(COND  ((ATOM x)   x)
   ((QUOTE T) (first (CAR x) )) )

должна обеспечивать выбор ветви в зависимости от атомарности значения аргумента. Семантика идеального Лиспа не определяет значение условного выражения при отсутствии предиката со значением "истина".

  • Остальные формы выражений рассматриваются по общей схеме как список из функции и ее аргументов. Обычно аргументы вычисляются и затем вычисленные значения передаются функции для интерпретации ее определения. Так обеспечивается возможность писать композиции функций. Например, в выражении (first (CAR x)) внутренняя функция CAR сначала получит в качестве своего аргумента значение переменной x, а потом свой результат передаст как аргумент более внешней функции first.
  • Если функция представлена своим названием , то среди названий различаются имена встроенных функций, такие как CAR, CDR, CONS и т.п., и имена функций, введенных в программе, например, first. Для встроенных функций интерпретатор сам знает как найти их значение по заданным аргументам, а для введенных в программе функций - использует их определение, которое находит по имени или по контексту.
  • Если функция использует лямбда-конструктор, то прежде, чем ее применять, понадобится связывать переменные из лямбда-списка со значениями аргументов. Функция, использующая лямбда-конструктор,
(LAMBDA (x)  (COND   ((ATOM x)   x)
    ((QUOTE T) (first (CAR x) ))   ))

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

  • Если представление функции начинается с DEFUN, то понадобится сохранить имя функции с соответствующим ее определением так, чтобы корректно выполнялись рекурсивные вызовы функции. Например, предыдущее LAMBDA -определение безымянной функции становится рекурсивным с помощью специальной функции DEFUN, первый аргумент которой – fisrt - имя новой функции.
(DEFUN first (x)   (COND   ((ATOM x)    x)
   ((QUOTE T) (first (CAR x) ))   ))

Можно сказать, что DEFUN замыкает выражение, содержащее функциональную переменную.

Таким образом, интерпретация функций осуществляется как взаимодействие четырех подсистем:

  • обработка структур данных ( cons, car, cdr, atom, eq ),
  • конструирование функциональных объектов ( lambda, defun ),
  • идентификация объектов (имена переменных и названия функций),
  • управление порядком вычислений (композиции, quote, cond, eval ).

Определение универсальной функции

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

(eval '(fn arg1 ... argK))

Результат применения " fn " к аргументам " arg1, ..., argK ".

Вычисление

Явное определение такой функции позволяет достичь четкости механизмов обработки Лисп-программ.

(eval '((LAMBDA (x y) (CONS (CAR x) y)) '(A B) '(C D) ))
= (A C D)

Вводим две основные функции eval и apply для обработки форм и обращения к функциям соответственно. Каждая из этих функций использует ассоциативный список для хранения связанных имен - значений переменных и определений функций. Сначала этот список пуст.

Вернемся к синтаксической сводке вычислимых форм.

<форма> ::= <переменная>
          | (QUOTE <S-выражение>)
          | (COND (<форма> <форма>) ... (<форма> <форма>))
          | (<функция> <аргумент> ...  <аргумент>)

<аргумент> ::= <форма>

<переменная> ::= <идентификатор>


<функция> ::= <название>
            | (LAMBDA <список_переменных> <форма>)
            | (DEFUN <название> <функция>)

<список_переменных> ::= (<переменная> ... )

<название> = <идентификатор>

<идентификатор> ::= <атом>
6.1. Синтаксическая сводка программ на языке Лисп

Каждой ветви этой сводки соответствует ветвь универсальной функции:

(DEFUN eval (e)   (ev e '((Nil . Nil))))

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

(defun ev (e a) (COND
      ( (atom e)  (cdr (assoc e a)) )
      ( (eq (car e) 'QUOTE)  (cadr e))
      ( (eq(car e) 'COND)    (evcon (cdr e) a))
      ( T  (apply (car e) (evlis (cdr e) a) a) ))    )

Поясним ряд пунктов этого определения.

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

Если CAR от формы - QUOTE, то она представляет собой константу, значение которой вычисляется как CADR от нее самой.

Если CAR от формы - COND, то форма - условное выражение. Вводим вспомогательную функцию EVCON, (определение ее будет дано ниже), которая обеспечивает вычисление предикатов (пропозициональных термов) по порядку и выбор формы, соответствующей первому предикату, принимающему значение "истина". Эта форма передается EV для дальнейших вычислений.

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

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

(defun apply (fn x a) (COND 
     ((atom fn) (cond 
         ((eq fn 'CAR)   (caar x))
         ((eq fn 'CDR)   (cdar x))
         ((eq fn 'CONS) (cons (car x) (cadr x)) )
         ((eq fn 'ATOM) (atom (car x)) )
         ((eq fn 'EQ)      (eq (car x) (cadr x)) )
         ((QUOTE  T)  (apply (ev fn a) x a)) ) )
                     )
((eq(car fn)'LAMBDA) (ev (caddr fn) (pairlis (cadr fn) x a) ))
((eq (car fn) 'DEFUN) (apply (cadddr fn) x (cons (cons (cadr fn)
      (cons 'LAMBDA  (caddr fn) )    ) a)    ))))

Первый аргумент apply - функция. Если она - атом, то существует две возможности: атом представляет одну из элементарных функций ( car cdr cons atom eq ). В таком случае соответствующая ветвь вычисляет значение этой функции на заданных аргументах. В противном случае, этот атом - имя ранее заданного определения, которое можно найти в ассоциативном списке.

Если функция начинается с LAMBDA, то ее аргументы попарно соединяются со связанными переменными, а тело определения (форма из лямбда-выражения) передается как аргумент функции EV для дальнейшей обработки.

Если функция начинается с DEFUN, то ее название и определение соединяются в пару и полученная пара размещается в ассоциативном списке, чтобы имя функции стало определенным при дальнейших вычислениях. Они произойдут как рекурсивный вызов apply, которая вместо имени функции теперь работает с ее определением при более полном ассоциативном списке - в нем теперь размещено определение названия функции. Поскольку определение размещается на "верху" стека, оно становится доступным для всех последующих переопределений, то есть работает как локальный объект. Глобальные объекты, такие как обеспечивает псевдо-функция DEFUN в системе Clisp, устроены немного иначе, что будет рассмотрено в следующей лекции.

< Лекция 5 || Лекция 6: 12 || Лекция 7 >