Defining new macros with define-modify-macro
One simple way to define new handy macros for our needs is define-modify-macro
. This is a handy macro which can create other macros for us.
Syntax:
define-modify-macro name lambda-list function [documentation]
⇒ name
We should supply name of new macro, list of parameters (not including place there) and symbol of function that will be used for processing.
Example of use:
(define-modify-macro togglef () not
"togglef modifies place, changing nil value to t and non-nil value to nil")
(define-modify-macro mulf (&rest args) *
"mulf modifies place, assigning product to it")
(define-modify-macro divf (&rest args) /
"divf modifies place, assigning result of division to it")
However, define-modify-macro
cannot be used for arbitrary processing. Here we have to take a look at other possibilities.
Function get-setf-expansion
Function get-setf-expansion
does not create any macros, but provides information which we can use to write our own.
Syntax:
get-setf-expansion place &optional environment
⇒ vars, vals, store-vars, writer-form, reader-form
As you can see, it returns a bunch of values, so it may be confusing at first sight. Let's try it on example:
CL-USER> (defvar *array* #(1 2 3 4 5))
*ARRAY*
CL-USER> (get-setf-expansion '(aref *array* 1))
; get-setf-expansion is a function, so we have to quote its argument
(#:G6029 #:G6030) ; list of variables needed to modify place
(*ARRAY* 1) ; values for these variables
(#:G6031) ; variable to store result of calculation
(SYSTEM::STORE #:G6029 ; writer-form: we should run it to modify place
#:G6030 ; ^
#:G6031) ; ^
(AREF #:G6029 #:G6030) ; reader-form: hm.. looks like our expression
Writing xf
macro
It seems like now we've got all information to write our xf
macro:
(defmacro xf (fn place &rest args &environment env)
(multiple-value-bind (vars forms var set access)
(get-setf-expansion place env)
(let ((g (gensym)))
`(let* ((,g ,fn) ; assign supplied function to generated symbol
,@(mapcar #'list vars forms) ; generate pairs (variable value)
(,(car var) (funcall ,g ,access ,@args))) ; call supplied function
; and save the result, we use reader-form here to get intial value
,set)))) ; just put writer-from here as provided
Note, that xf
macro takes evironment variable and pass it to get-setf-expansion
. This variable is needed to ensure that any lexical bindings or definitions established in the compilation environment are taken into account.
Let's try it:
CL-USER> (defvar *var* '(("foo" . "bar") ("baz" . "qux")))
*VAR*
CL-USER> (xf #'reverse (cdr (second *var*)))
"xuq"
CL-USER> *var*
(("foo" . "bar") ("baz" . "xuq"))
Expansion:
(LET* ((#:G6033 #'REVERSE)
(#:TEMP-6032 (SECOND *VAR*))
(#:NEW-6031 (FUNCALL #:G6033
(CDR #:TEMP-6032))))
(SYSTEM::%RPLACD #:TEMP-6032 #:NEW-6031))
I hope this information is useful.
This answer is based on Paul Graham's On Lisp, section 12.4 More Complex Utilities.