На этом шаге мы продолжим рассмотривать средства, позволяющие создавать поименованные функции.
Определить новую функцию и дать ей имя можно с помощью функции DEFUN
(DEfine FUNction). Функция DEFUN вызывается так:
(DEFUN ATOM LAMBDA-выражение)
Функция DEFUN соединяет атом ATOM с LAMBDA-выражением, и ATOM начинает представлять (именовать) определенные этим лямбда-выражением вычисления.
Функция DEFUN возвращает имя новой функции.
Формальные параметры функции называют еще лексическими или статическими переменными. Связи статической переменной действительны только в пределах той функции, в которой они определены. Они перестают действовать в функциях, вызываемых во время вычисления, но текстуально описанных вне данной функции. После вычисления функции, созданные на это время связи формальных параметров ликвидируются и происходит возврат к тому состоянию, которое было до вызова функции.
Возникшие в результате побочного эффекта изменения значений свободных переменных, т.е. используемых в функции, но не входящих в число ее статических переменных, остаются в силе после окончания выполнения функции.
Программа на языке LISP состоит из множества коротких функций. Функции вызывают друг друга в единообразной форме, которая определяется использованием лямбда-механизма. В теле лямбда-выражения предусмотрена возможность динамического вызова функциями друг друга или рекурсивного вызова функцией самой себя (непосредственно или опосредованного).
Порядок следования определений функций в тексте программы не имеет никакого значения с точки зрения логики программы, т.к. порядок вычислений в функциональном программировании непосредственно не выражен. Ход выполнения программы и необходимое для нее время и память проявятся лишь во время ее выполнения.
(DEFUN SUMMA (LAMBDA (X Y) (PLUS X Y))) % --------------------- % (DEFUN ADD1 (LAMBDA (NUM) % Функция инкремента % (PLUS NUM 1) )) % --------------------- % (DEFUN SUB1 (LAMBDA (NUM) % Функция декремента % (DIFFERENCE NUM 1) )) % ------------------------ % (DEFUN PROCENT (LAMBDA (A B) % Функция возвращает A процентов от числа B % (QUOTENT (TIMES A 100) B) )) % -------------------- % (DEFUN ABS (LAMBDA (NUM) % Функция ABS возвращает абсолютное значение % % аргумента NUM % (COND ( (MINUSP NUM) (MINUS NUM)) ( T NUM ) ) )) % -------------------- % (DEFUN MAX (LAMBDA (M N) % Функция MAX возвращает большее из двух чисел % (COND ( (GREATERP M N) M ) ( T N ) ) )) % ---------------------------------------------- % (DEFUN KPLUS (X Y) % X и Y - списки вида (x y) % % Фрагмент комплексной арифметики % (LIST (PLUS (CAR X) (CAR Y)) (PLUS (CADR X) (CADR Y))) ))
(DEFUN LISTQ (NLAMBDA (X Y) (LIST X Y) ))Тестовый пример:
$ (LISTQ ((PLUS 3 4) (TIMES 5 6))) ((PLUS 3 4) (TIMES 5 6))Текст этой функции можно взять здесь.
(IF CONDITION THEN P ELSE Q)и опишем соответствующую невычисляемую функцию:
(DEFUN IF (NLAMBDA (CONDITION THEN P ELSE Q) (COND ( (EVAL CONDITION) P ) ( T Q ) ) ))Тестовые примеры:
$ (SETQ X '(ALEA JACTA EST)) (ALEA JACTA EST) $ (IF (ATOM X) THEN OREL ELSE RESHKA) RESHKAТекст этой функции можно взять здесь.
Мы не проверяем, правильно ли указаны и на своих ли местах атомы THEN и ELSE, но такую проверку можно было бы добавить достаточно просто.
Замечание 2. В языке LISP нет различия между программами и данными: и программа, и данные транслируются во внутреннее представление, имеющее вид связанных списков (имена всех встроенных функций языка LISP суть атомы, которые определены системой в начале выполнения, но которые в остальном выглядят так же, как любые другие атомы). Это позволяет LISP-программам динамически создавать другие LISP-программы и затем выполнять их. Таким образом, если потребуется, список данных можно превратить в список-программу и наоборот.
Например, изобразим в графической нотации список, соответствующий следующей функции
языка LISP:
(LAMBDA (X) (COND ((NULL X) NIL) (T (CAR (CDR X)))))
Рис.1. Графическое представление списка
Замечание 3. Начальные выражения используются для манипулирования определениями функций. Когда функция определяется, это определение автоматически псевдокомпилируется в сильно сжатую, компактную форму - так называемый D-код.
Обратный процесс декомпиляции определений функций в связанный список осуществляется автоматически с использованием функции GETD для восстановления определений.
Следовательно, и компиляция, и декомпиляция невидимы для пользователя.
Специальная форма для определения функций DEFUN вызывается двумя способами. Первый способ более краткий, он может использоваться только для определения функций. Второй способ, кроме того, может использоваться для определения невычисляемых функций и макросов.
Функция (DEFUN SYMBOL ARGLIST FORM1 ...FORMN) компилирует ARGLIST и формирует его на D-коде, предполагая, что тип функции - вычисляемая или LAMBDA. Затем функция DEFUN модифицирует элемент определения функции SYMBOL на указание на результирующий D-код и возвращает SYMBOL.
Если FTYPE есть символ LAMBDA, NLAMBDA или MACRO, то функция (DEFUN SYMBOL (FTYPE ARGLIST FORM1 ... FORMN)) компилирует ARGLIST и формирует его на D-коде, предполагая, что FTYPE - это определение вычисляемой, невычисляемой или макро-функции. Затем DEFUN модифицирует функциональный элемент SYMBOL на указание на результирующий D-код и возвращает SYMBOL.
Если ADDRESS равен нулю или является положительным целым, числом меньшим, чем 65536, то функция (DEFUN SYMBOL ADDRESS) модифицирует элемент функции SYMBOL на указание на ADDRESS и возвращает SYMBOL.
Реализация функции приведена ниже:
(DEFMACRO DEFUN (SYMBOL . BODY) ( (ATOM BODY) NULL ) ( (NUMBERP (CAR BODY)) (LIST 'PUTD (LIST 'QUOTE SYMBOL) (CAR BODY)) ) ( (MEMBER (CAAR BODY) '(LAMBDA NLAMBDA MACRO)) (LIST 'PUTD (LIST 'QUOTE SYMBOL) (CONS 'QUOTE BODY)) ) ( LIST 'PUTD (LIST 'QUOTE SYMBOL) (LIST 'QUOTE (CONS 'LAMBDA BODY))) )
На следующем шаге мы продолжим знакомство с поименованными функциями, в частности, рассмотрим создание макросов в системе muLISP85.