Явное определение такой функции позволяет достичь четкости механизмов обработки Лисп-программ.
(eval '((LAMBDA (x y) (CONS (CAR x) y)) '(A B) '(C D) )) = (A C D)
Вводим две основные функции eval и apply для обработки форм и обращения к функциям соответственно. Каждая из этих функций использует ассоциативный список для хранения связанных имен - значений переменных и определений функций. Сначала этот список пуст.
Вернемся к синтаксической сводке вычислимых форм.
<форма> ::= <переменная> | (QOUTE <S-выражение>) | (COND (<форма> <форма>) ... (<форма> <форма>)) | (<функция> <аргумент> ... <аргумент>)
<аргумент> ::= <форма>
<переменная> ::= <идентификатор>
<функция> ::= <название> | (LAMBDA <список_переменных> <форма>) | (DEFUN <название> <функция>)
<список_переменных> ::= (<переменная> ... )
<название> = <идентификатор>
<идентификатор> ::= <атом>
Пример 6.1. Синтаксическая сводка программ на языке Лисп (html, txt)
Каждой ветви этой сводки соответствует ветвь универсальной функции:
(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, устроены немного иначе, что будет рассмотрено в следующей лекции.
assoc и pairlis уже определены ранее.
(DEFUN evcon (c a) (COND ((ev (caar c) a) (ev (cadar c) a) ) ( T (evcon (cdr c) a) ) ))
*) Примечание. В идеальном Лиспе не допускается отсутствие истинного предиката, т.е. пустого C.
(DEFUN evlis (m a) (COND ((null m) Nil ) ( T (cons(ev (car m) a) (evlis(cdr m) a) )) )
При
(DEFUN eval (e) (ev e ObList ))
определения функций могут накапливаться в системной переменной ObList, тогда они могут работать как глобальные определения. ObList обязательно должна содержать глобальное определение встроенной константы Nil, можно разместить в ней и представление истины - T.
Определение универсальной функции является важным шагом, показывающим одновременно и механизмы реализации языков программирования, и технику функционального программирования на любом языке. Пока еще не описаны многие другие особенности языка Лисп, которые будут рассмотрены позднее. Но все они будут увязаны в единую картину, основа которой согласуется с этим определением.