Макрос расширяется до того же оператора - PullRequest
4 голосов
/ 08 апреля 2020

Многие языки семейства Lisp имеют немного синтаксического сахара для таких вещей, как сложение или сравнение, допускающее более двух операндов, if опционально опуская альтернативную ветвь et c. Можно было бы кое-что сказать об их реализации с помощью макросов, что расширило бы (+ a b c) до (+ a (+ b c)) и т. Д .; это сделает реальный код выполнения чище, проще и немного быстрее (потому что logi c для проверки дополнительных аргументов не нужно будет запускать каждый раз, когда вы добавляете пару чисел).

Однако обычный Алгоритм раскрытия макроса - «продолжайте расширять внешнюю форму снова и снова, пока не получите немарокрасочный результат». Это означает, что, например, + лучше не быть макросом, который расширяется до +, даже уменьшенной версией, или вы получите бесконечное l oop.

. Есть ли какой-нибудь существующий Лисп, который решает эту проблему? во время расширения макроса? Если да, то как он это делает?

Ответы [ 2 ]

9 голосов
/ 08 апреля 2020

Это дополнение к ответу Райнера: этот ответ на самом деле просто дает несколько примеров.

Прежде всего, компиляция таких вещей, как арифметика c операции - дело волосатое, потому что у вас есть особый стимул попробовать и повернуть как можно больше в операциях, которые машина понимает и не может выполнить, что может привести к огромному замедлению числового кода. Поэтому, как правило, компилятор имеет много знаний о том, как компилировать вещи, и ему также предоставляется большая свобода: например, в CL (+ a 2 b 3) может быть преобразован компилятором в (+ 5 a b): компилятору разрешено переупорядочивать & объединить вещи (но не для того, чтобы изменить порядок оценки: он может превратить (+ (f a) (g b)) в нечто вроде (let ((ta (f a)) (tb (g b))) (+ tb ta)), но не в (+ (g b) (f a))).

Так что арифметика c обычно довольно велика c. Но все еще интересно посмотреть, как вы можете сделать это с макросами и зачем вам нужны макросы компилятора в CL.

(Примечание: все нижеприведенные макросы - это то, что я написал без особого размышления: они могут быть семантически неправильно.)

Макросы: неправильный ответ

Итак, кроме того, в кл. Один очевидный трюк состоит в том, чтобы иметь функцию 'primitive-two-arg' (которая, по-видимому, компилятор может встроить в сборку в хороших случаях), а затем иметь интерфейс publi c в виде макроса, который расширяется до этого.

Итак, вот что

(defun plus/2 (a b)
  ;; just link to the underlying CL arithmetic
  (+ a b))

И затем вы можете написать общую функцию в терминах этого очевидным образом:

(defun plus/many (a &rest bcd)
  (if (null bcd)
      a
    (reduce #'plus/2 bcd :initial-value a)))

И теперь вы можете написать публичные c интерфейс, plus как макрос поверх этого:

(defmacro plus (a &rest bcd)
  (cond ((null bcd)
         a)
        ((null (rest bcd))
         `(plus/2 ,a ,(first bcd)))
        (t
         `(plus/2 (plus/2 ,a ,(first bcd))
                  (plus ,@(rest bcd))))))

И вы можете видеть, что

  • (plus a b) расширяется до (plus/2 a b) '
  • (plus a b c) расширяется до (plus/2 (plus/2 a b) (plus c)), а затем до (plus/2 (plus/2 a b) c).

И мы можем сделать лучше, чем это:

(defmacro plus (a &rest bcd)
  (multiple-value-bind (numbers others) (loop for thing in (cons a bcd)
                                             if (numberp thing)
                                             collect thing into numbers
                                             else collect thing into things
                                             finally (return (values numbers things)))
    (cond ((null others)
           (reduce #'plus/2 numbers :initial-value 0))
          ((null (rest others))
           `(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
                    ,(first others)))
          (t
           `(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
                    ,(reduce (lambda (x y)
                               `(plus/2 ,x ,y))
                             others))))))

И теперь вы можете расширить Например, (plus 1 x y 2.0 3 z 4 a) в (plus/2 10.0 (plus/2 (plus/2 (plus/2 x y) z) a)), что, мне кажется, выглядит нормально для меня.

Но это безнадежно . Это безнадежно, потому что, если я скажу (apply #'plus ...)? Doom: plus должна быть функцией, это не может быть макрос.

Макросы компилятора: правильный ответ

И вот тут вступают макросы компилятора. Давайте начнем снова, но на этот раз функция (никогда не использовавшаяся выше) plus/many будет просто plus:

(defun plus/2 (a b)
  ;; just link to the underlying CL arithmetic
  (+ a b))

(defun plus (a &rest bcd)
  (if (null bcd)
      a
    (reduce #'plus/2 bcd :initial-value a)))

И теперь мы можем написать макрос компилятора для plus, который является специальным макросом, который может быть используется компилятором:

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

(define-compiler-macro plus (a &rest bcd)
  (multiple-value-bind (numbers others) (loop for thing in (cons a bcd)
                                             if (numberp thing)
                                             collect thing into numbers
                                             else collect thing into things
                                             finally (return (values numbers things)))
    (cond ((null others)
           (reduce #'plus/2 numbers :initial-value 0))
          ((null (rest others))
           `(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
                    ,(first others)))
          (t
           `(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
                    ,(reduce (lambda (x y)
                               `(plus/2 ,x ,y))
                             others))))))

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

Вы можете проверить расширение с помощью compiler-macroexpand:

> (compiler-macroexpand '(plus 1 2 3 x 4 y 5.0 z))
(plus/2 15.0 (plus/2 (plus/2 x y) z))
t

Второй значение указывает, что макрос компилятора не отклонял расширение. И

> (apply #'plus '(1 2 3))
6

Так что это выглядит хорошо.

В отличие от обычных макросов такой макрос может отклоняться, чтобы расширяться, и это происходит путем возврата всей формы макроса без изменений , Например, вот версия вышеупомянутого макроса, которая имеет дело только с очень простыми случаями:

(define-compiler-macro plus (&whole form a &rest bcd)
  (cond ((null bcd)
         a)
        ((null (rest bcd))
         `(plus/2 ,a ,(first bcd)))
        (t                              ;cop out
         form)))

А теперь

 > (compiler-macroexpand '(plus 1 2 3 x 4 y 5.0 z))
(plus 1 2 3 x 4 y 5.0 z)
nil

, но

> (compiler-macroexpand '(plus 1 2))
(plus/2 1 2)
t

OK.

9 голосов
/ 08 апреля 2020

Common Lisp предоставляет макросы компилятора .

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

...