2015-07-23 4 views
3

В Section 12.4 of On Lisp, Пол Грэм пишет, «К сожалению, мы не можем определить правильный _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) 

Имеет ли возможно, было изменение, сделанное определение-изменение-макросов в ANSI Common Lisp, который не был в силе в то время на Лиспе было написано? Или есть причины, отличные от тех, которые указаны для использования здесь define-modify-macro?

Похоже, что Грэм хочет, чтобы иметь возможность сделать звонок, такие как

(_f * (second lst) 6) 

, а не

(_f #'* (second lst) 6) 

Но, конечно, это не в соответствии с Lisp2, таких как Common Lisp?

ответ

2

Согласно и Lispworks's Hyperspec и CLtL2 (ищет 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 и дает желаемую функцию в качестве аргумента этой функции. На первый взгляд, это выглядит как взломать, но, как мы видим ниже, это дает тот же макроэкранный код, что и в На Lisp, предполагая, что мы немного модифицируем его.

macroexpansion вышеперечисленное:

(LET ((X (LIST 1 2 3 4))) 
    (LET* ((#:G1164 X) (#:G1165 (FUNCALL #'+ (THIRD #:G1164) 10))) 
    (SB-KERNEL:%RPLACA (CDDR #:G1164) #:G1165)) 
    X) 

Версия в На Лисп ведет себя следующим образом:

(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) 

Здесь * находится вводится непосредственно макросом pansion, что означает, что полученный код не имеет возможности для выполнения во время выполнения (хотя компиляторы, вероятно, будут обрабатывать ваш (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) 

Теперь, это именно так, как ваша версия. На Lisp использует _f, чтобы продемонстрировать, как использовать get-setf-expansion, и _f - хороший пример для этого. Но, с другой стороны, ваша реализация кажется одинаково хорошей.

1

На вопрос о том, может ли один предпочесть пройти * или #'*, мы можем также отметить, что версия define-modify-macro из _f и адаптированный вариант @ CoreDump (с кодом funcall) оба принимают лямбда-формы в положении оп с или без #' например оба (lambda (x y) (* x y)) и #'(lambda (x y) (* x y)), тогда как оригинальная версия Грэма принимает только первые.

Интересно, что в своей книге Let over Lambda, Дуги Хойт обращает внимание на замечание Грэма в своей книге ANSI Common Lisp, что, будучи в состоянии опустить #' до формы лямбды обеспечивает «благовидную форму элегантности в лучшем случае» перед выходом на предпочитают его пропускать.

Я не занимаю позицию в любом случае, просто указывая на то, что, учитывая выбор Грэма для _f, отсутствие #' уже не является призрачным, но необходимым.

Смежные вопросы