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, отсутствие #' больше не показно, но необходимо.

Другие вопросы по тегам