Шаг 18.
Функции пользователя. Макросы в системе muLISP85

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

    Часто бывает полезно не выписывать вычисляемое выражение вручную, а сформировать его с помощью программы. При этом вычисление такого выражения или его части при необходимости можно предотвратить блокировкой (QUOTE), например, с целью преобразования выражений. Для вычисления же сформированного выражения программист всегда может вызвать интерпретатор EVAL.

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

    При вызове макроса вначале из его аргументов строится некоторое выражение, задаваемое определением макроса. Затем это выражение вычисляется. Таким образом, макрос вычисляется как-бы в два этапа.

    Функция


    (DEFMACRO SYMBOL ARGLIST FORM1 ... FORMN)

деструктурирует и компилирует список аргументов ARGLIST и все формы FORM1,...,FORMN (называемые телом макроса) в D-код, модифицирует элемент определения функции SYMBOL на указатель на результирующий D-код и возвращает SYMBOL.


    Примеры.
   (DEFMACRO INCQ1 (SYM N)
      ( (NUMBERP N)
           (LIST 'SETQ SYM (LIST '+ SYM N)) )
      (LIST 'SETQ SYM (LIST 'ADD1 SYM))
   )
   ; ----------------
   (DEFMACRO AND1 LST
      ( (NULL LST) )
      ( (NULL (CDR LST)) (CAR LST) )
   )
   ; ---------------
   (DEFMACRO OR1 LST
      ( (NULL LST) NIL )
      ( CONS 'COND
             (MAPCAR '(LAMBDA (FORM) (LIST FORM)) LST))
   )

    В отличие от функции DEFUN, функция DEFMACRO обеспечивает деструктурирование списка аргументов ARGLIST. Это означает, что аргументы закрепляются за символами в ARGLIST на основе структуры ARGLIST в виде связанного списка.


    Пример. В качестве примера отличия макроса от функции рассмотрим реализацию операции добавления элемента в стек, называемую PUSH, запрограммированную сначала в виде функции, а затем посредством макроса:

   (DEFUN PUSH1 (A P)
      (SETQ P (CONS A (EVAL P)))
   )
   ; -------------------
   (DEFMACRO PUSH2 (A P)
      (LIST 'SETQ P (LIST 'CONS A P))
   )
   $ (SETQ S '(B C))
   (B C)
   $ (PUSH1 'A 'S)
   (A B C)
   $ S
   (B C)
Заметим, что при вызове функции PUSH1 в аргументах используются апострофы. Продолжим тестирование:

   $ (PUSH2 D S)
   (D B C)
   $ S
   (D B C)
Текст этих функции и макроса можно взять здесь.

    Апостроф при обращении к функции теперь не нужен!

    Если ARGLIST есть символ, то фактические аргументы в макровызове связаны с символами. С другой стороны, CAR-элементы аргументов рекурсивно связаны с CAR-элементами ARGLIST, а CDR-элементы аргументов - с CDR-элементами ARGLIST.

    Вызов макро (макровызов) представляет собой список форм


     (MACRO FORM1 FORM2 ...  FORMN),
где: MACRO - имя макрофункции, FORM1,FORM2,...,FORMN - фактические параметры макроса MACRO.

    Например, определим макрос:


   (DEFMACRO SETQQ (X Y)
      (LIST 'SETQ X (LIST 'QUOTE Y))
   )
и вызовем его:

   $ (SETQQ Q (A S D))
   (A S D)
   $ Q
   (A S D)
Текст этого макроса можно взять здесь.

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

    Если FORMA есть макровызов, то функция


   (MACROEXPAND-1 FORMA)
выполняет расширение макровызова FORMA (осуществляет действия макрофункции над FORMA) и возвращает результат. В противном случае MACROEXPAND-1 возвращает FORMA.

    Например, определим следующий макрос:


   (DEFMACRO DECQ1 (SYM N)
      ( (NUMBERP N)
           (LIST 'SETQ SYM (LIST '- SYM N)) )
      (LIST 'SETQ SYM (LIST 'SUB1 SYM))
   )
и выполним тестовый пример:

   $ (SETQ A 92)
   92
   $ (DECQ1 A 2)
   90
   $ (MACROEXPAND-1 '(DECQ1 A 2))
   (SETQ A (-A 2))
Текст этого макроса можно взять здесь.


    Приведем еще один пример. Определим рекурсивный макрос:

   (DEFMACRO COPY-LISTQ (X)
      (COND ( (NULL X) NIL )
            (  T  (LIST 'CONS (LIST 'QUOTE (CAR X))
                              (LIST 'COPY-LISTQ (CDR X))
                  )
            )
      )
   )
и обратимся к функции MACROEXPAND-1:

   $ (COPY-LISTQ (A S D))
   (A S D)
   $ (MACROEXPAND-1 '(COPY-LISTQ (A S D)))
   (CONS (QUOTE A) (COPY-LISTQ (S D)))
Текст этого макроса можно взять здесь.

    Если FORMA является макровызовом, то функция


     (MACROEXPAND FORMA)
повторно расширяет FORMA до тех пор, пока результат расширения не будет макровызовом.

    Функция MACROEXPAND возвращает результат последнего расширения.

    Хотя интерпретатор muLISP автоматически расширяет макровызовы во время выполнения (когда функция вычисляется), обычно удобнее осуществлять расширение макросов только один раз - во время компиляции. Для осуществления макрорасширения во время компиляции макрос должен быть определен перед определением функции, содержащей макровызов, а управляющая переменная MACROEXPAND должна иметь значение не NIL.

    Если управляющая переменная MACROEXPAND не равна NIL, то макровызовы расширяются так же, как и функцией MACROEXPAND во время компиляции (когда функция определяется с помощью DEFUN или PUTD).

    Если управляющая переменная MACROEXPAND есть NIL, то макровызовы не расширяются во время компиляции.


    Замечание 1. Реализация функций DEFMACRO, MACROEXPAND-1 и MACROEXPAND приведена ниже:
   (PUTD DEFMACRO 'MACRO BODY
      (POP BODY)
      (LIST 'PUTD
            (LIST 'QUOTE (CAR BODY))
            (LIST 'QUOTE
                  (LIST 'MACRO
                        'BODY
                        (CONS (LIST* 'LAMBDA
                                      (Leaves (CADR BODY))
                                      (CDDR BODY)
                              )
                              (Destructure (CADR BODY)
                                           '(CDR BODY))
                        ))))
   )
   ; ------------------
   (DEFUN Leaves (TREE)
      ( (ATOM TREE) (LIST TREE) )
      ( (NULL (CDR TREE)) (Leaves (CAR TREE)) )
      ( NCONC (Leaves (CAR TREE)) (Leaves (CDR TREE)))
   )
   ; ----------------------------
   (DEFUN Destructure (TREE FORM)
      ( (ATOM TREE) (LIST FORM) )
      ( (NULL (CDR TREE))
           (Destructure (CAR TREE) (LIST 'CAR FORM)) )
      ( NCONC (Destructure (CAR TREE) (LIST 'CAR FORM))
           (Destructure (CDR TREE) (LIST 'CDR FORM)) )
   )
   ; -------------------------
   (DEFUN MACROEXPAND-1 (FORM)
      ( (ATOM FORM) FORM )
      ( (NOT (SYMBOLP (CAR FORM))) FORM )
      ( (NOT (EQ 'MACRO (GETD (CAR FORM) T))) FORM )
      ( APPLY (CAR FORM) FORM )
   )
   ; --------------------------------------
Приведем реализацию функции MACROEXPAND:
   (DEFUN MACROEXPAND (FORM)
      ( (ATOM FORM) FORM )
      ( (NOT (SYMBOLP (CAR FORM))) FORM )
      ( (NOT (EQ 'MACRO (GETD (CAR FORM) T))) FORM )
      ( MACROEXPAND (APPLY (CAR FORM) FORM) )
   )
Замечание 2. Определение функции в версии muLISP85 есть список, состоящий из трех частей:

    CAR-элементом определения функции является наименование типа функции, т.е. LAMBDA, NLAMBDA или MACRO. Тип функции дает интерпретатору информацию о том, как использовать данную функцию.

    Функция типа LAMBDA называется вычисляемой. Если вызывается вычисляемая функция, то каждый из ее аргументов вычисляется, а самой функции передается только его значение.

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

    Функция типа MACRO называется макро-функцией. Если вызывается макро-функция, то сначала используется макроопределение для правильного вызова макроса без вычисления. Результат таких макрорасширений затем вычисляется и выдается как значение макроса. Контроль за макросами осуществляет управляющая переменная MACROEXPAND.

    CADR-элементом определения функции в системе muLISP85 является:

    Функция, определенная с помощью списка формальных аргументов (в том числе пустого), называется развернутой. Если вызывается такая функция, то фактические аргументы связываются с соответственными формальными аргументами.

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

    Отметим, что в системе muLISP85 решение сделать функцию развернутой или нет принимается независимо от решения сделать ее вычисляемой или нет.

    CDDR-элементом определения функции в системе muLISP85 является список, который называется телом функции.

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

    На следующем шаге мы введем понятие функционала.




Предыдущий шаг Содержание Следующий шаг