Это дополнение к ответу Райнера: этот ответ на самом деле просто дает несколько примеров.
Прежде всего, компиляция таких вещей, как арифметика 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.