In Section 12.4 of On Lisp, Paul Graham writes, "Unfortunately, we can't define a correct _f with define-modify-macro, because the operator to be applied to the generalized variable is given as an argument."
But what's wrong with something like this?
(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)
Has there perhaps been a change made to define-modify-macro in ANSI Common Lisp that wasn't valid at the time On Lisp was written? Or are there reasons other than the one stated for not using define-modify-macro here?
It appears that Graham want's to be able to make a call such as
(_f * (second lst) 6)
rather than
(_f #'* (second lst) 6)
But surely that's not in keeping with a Lisp2 such as Common Lisp?
According to both Lispworks's Hyperspec and CLtL2 (look for define-modify-macro), the function is assumed to be a symbol (to a function or a macro). As far as I know, the following definition might not be conforming the specification:
(define-modify-macro _f (op operand)
(lambda (x op operand)
(funcall op x operand)))
But of course, it is possible that an implementation allows it. To be sure you are conforming to the standard, you can define your own function, or even a macro:
(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)
If you wanted to have the function as a second argument, you could define another macro:
(defmacro ff (fun-form place &rest args)
`(_ff ,place ,fun-form ,@args))
Basically, your approach consists in wrapping funcall in define-modify-macro, and give the desired function as an argument of that function. At first sight, it looks like a hack, but as we can see below, this gives the same macroexanded code as the one in On Lisp, assuming we modify the latter a little.
The macroexpansion of the above is:
(LET ((X (LIST 1 2 3 4)))
(LET* ((#:G1164 X) (#:G1165 (FUNCALL #'+ (THIRD #:G1164) 10)))
(SB-KERNEL:%RPLACA (CDDR #:G1164) #:G1165))
X)
The version in On Lisp behaves as follows:
(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)
Here, the * is injected directly by the macroexpansion, which means that the resulting code has no possible runtime overhead (though compilers would probably handle your (funcall #'+ ...) equally well). If you pass #'+ to the macro, it fails to macroexpand. This is the major difference with your approach, but not a big limitation. In order to allow the On Lisp version to accept #'*, or even (create-closure) as an operator, it should be modified as follows:
(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)))
(see the call to funcall)
The previous example is then expanded as follows, for #'*:
(LET ((X (LIST 1 2 3 4)))
(LET* ((#:G1180 X) (#:G1181 (FUNCALL #'* (THIRD #:G1180) 10)))
(SB-KERNEL:%RPLACA (CDDR #:G1180) #:G1181))
X)
Now, it is exactly as your version. On Lisp uses _f to demonstrate how to use get-setf-expansion, and _f is a good example for that. But on the other hand, your implementation seems equally good.
On the question of whether one might prefer to pass * or #'*, we can also note that the define-modify-macro version of _f and @coredump's adapted version (with funcall) both accept lambda forms in the op position with or without #' e.g. both (lambda (x y) (* x y)) and #'(lambda (x y) (* x y)), whereas Graham's original version accepts only the former.
Interestingly in his book Let over Lambda, Doug Hoyte draws attention to a remark by Graham in his book ANSI Common Lisp that being able to omit the #' before a lambda form provides "a specious form of elegance at best" before going on to prefer to omit it.
I'm not taking a stand either way, merely pointing out that given Graham's choice for _f, the absence of the #' is no longer specious but necessary.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With