define-modify-macro с операторным аргументом
В Разделе 12.4 "На Лиспе" Пол Грэм пишет: "К сожалению, мы не можем определить правильный _f
с define-modify-macro
потому что оператор, который будет применен к обобщенной переменной, задан в качестве аргумента."
Но что не так с чем-то вроде этого?
(define-modify-macro _f (op operand)
(lambda (x op operand)
(funcall op x operand)))
(let ((lst '(1 2 3)))
(_f (second lst) #'* 6)
lst)
=> (1 12 3)
Возможно, было внесено изменение в define-modify-macro в ANSI Common Lisp, которое было недействительным на момент написания On Lisp? Или есть причины, отличные от той, которая не указана define-modify-macro
Вот?
Похоже, что Грэм хочет, чтобы можно было сделать звонок, такой как
(_f * (second lst) 6)
скорее, чем
(_f #'* (second lst) 6)
Но, конечно же, это не соответствует Lisp2, например Common Lisp?
2 ответа
В соответствии с Hyperspec и CLtL2 Lispworks (ищите define-modify-macro
) предполагается, что функция является символом (для функции или макроса). Насколько я знаю, следующее определение может не соответствовать спецификации:
(define-modify-macro _f (op operand)
(lambda (x op operand)
(funcall op x operand)))
Но, конечно, возможно, что реализация это позволяет. Чтобы убедиться, что вы соответствуете стандарту, вы можете определить свою собственную функцию или даже макрос:
(defmacro funcall-1 (val fun &rest args)
`(funcall ,fun ,val ,@args))
(define-modify-macro _ff (&rest args) funcall-1)
(let ((x (list 1 2 3 4)))
(_ff (third x) #'+ 10)
x)
Если вы хотите иметь функцию в качестве второго аргумента, вы можете определить другой макрос:
(defmacro ff (fun-form place &rest args)
`(_ff ,place ,fun-form ,@args))
По сути, ваш подход заключается в упаковке funcall
в define-modify-macro
и дать желаемую функцию в качестве аргумента этой функции. На первый взгляд, это выглядит как хак, но, как мы можем видеть ниже, это дает тот же макроэкспандированный код, что и в On Lisp, при условии, что мы немного изменим последний.
Макроразложение вышеперечисленного:
(LET ((X (LIST 1 2 3 4)))
(LET* ((#:G1164 X) (#:G1165 (FUNCALL #'+ (THIRD #:G1164) 10)))
(SB-KERNEL:%RPLACA (CDDR #:G1164) #:G1165))
X)
Версия в On Lisp ведет себя следующим образом:
(defmacro _f (op place &rest args)
(multiple-value-bind (vars forms var set access)
(get-setf-expansion
place)
`(let* (,@(mapcar #'list vars forms)
(, (car var) (,op ,access ,@args)))
,set)))
(let ((x (list 1 2 3 4)))
(_f * (third x) 10)
x)
Macroexpansion:
(LET ((X (LIST 1 2 3 4)))
(LET* ((#:G1174 X) (#:G1175 (* (THIRD #:G1174) 10)))
(SB-KERNEL:%RPLACA (CDDR #:G1174) #:G1175))
X)
Здесь *
внедряется непосредственно с помощью макроэкспонирования, что означает, что результирующий код не имеет возможных накладных расходов во время выполнения (хотя компиляторы, вероятно, будут обрабатывать ваши (funcall #'+ ...)
Одинаково хорошо). Если вы пройдете #'+
к макросу, он не может макрорасширить. Это главное отличие вашего подхода, но не большое ограничение. Чтобы позволить версию на Лиспе принять #'*
, или даже (create-closure)
как оператор, он должен быть изменен следующим образом:
(defmacro _f (op place &rest args)
(multiple-value-bind (vars forms var set access)
(get-setf-expansion
place)
`(let* (,@(mapcar #'list vars forms)
(, (car var) (funcall ,op ,access ,@args)))
,set)))
(см. призыв к funcall
)
Предыдущий пример затем расширяется следующим образом: #'*
:
(LET ((X (LIST 1 2 3 4)))
(LET* ((#:G1180 X) (#:G1181 (FUNCALL #'* (THIRD #:G1180) 10)))
(SB-KERNEL:%RPLACA (CDDR #:G1180) #:G1181))
X)
Теперь это именно так, как ваша версия. На Лиспе использует _f
продемонстрировать, как использовать get-setf-expansion
, а также _f
хороший пример для этого. Но с другой стороны, ваша реализация кажется одинаково хорошей.
На вопрос, может ли кто-то предпочесть пройти *
или же #'*
Также можно отметить, что define-modify-macro
версия _f
и адаптированная версия @ coredump (с funcall
) оба принимают лямбда-формы в операционном положении с или без #'
например, оба (lambda (x y) (* x y))
а также #'(lambda (x y) (* x y))
в то время как оригинальная версия Грэма принимает только первое.
Интересно, что в своей книге Let over Lambda Даг Хойт обращает внимание на замечание Грэма в своей книге ANSI Common Lisp о том, что он может опустить #'
перед тем как лямбда-форма обеспечивает "лучшую форму элегантности в лучшем случае", прежде чем предпочесть опустить ее.
В любом случае, я не отстаиваю позицию, а просто указываю на то, что, учитывая выбор Грэм _f
, отсутствие #'
больше не показно, но необходимо.