How to change location with arbitrary function

Sometimes we need to change place , but there is no built-in function that meets our needs.

For example, here is incf and decf for addition and subtraction:

 CL-USER> (defvar *x* 5) *X* CL-USER> (incf *x* 3) 8 CL-USER> *x* 8 CL-USER> (decf *x* 10) -2 CL-USER> *x* -2 

But what about multiplication and division? What if we want to change the place with an arbitrary function, for example:

 (xf (lambda (x) ...) *x*) 

xf utility would be very useful, especially when we had to deal with deeply nested structures:

 (my-accessor (aref (cdr *my-data*) n)) 
+7
macros variable-assignment lisp common-lisp
source share
2 answers

Defining new macros with define-modify-macro

One easy way to define new convenience macros for our needs is define-modify-macro . This is a handy macro that can create other macros for us.

Syntax:

define-modify-macro lambda list function name [documentation]

⇒ name

We must specify the name of the new macro, a list of parameters (not including the place there) and the function symbol that will be used for processing.

Usage example:

 (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 must take a look at other possibilities.

get-setf-expansion function

The get-setf-expansion function does not create any macros, but provides information that we can use to write our own.

Syntax:

get-setf-expand space and additional environment

⇒ vars, ramparts, vaults, writing form, reader form

As you can see, it returns a bunch of values, so at a glance it can be confusing. Let's try this with an 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 an xf Macro

It seems we now have all the 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 the xf macro accepts the evironment variable and passes it to get-setf-expansion . This variable is necessary to ensure that any lexical bindings or definitions are set up in the compilation environment.

Try:

 CL-USER> (defvar *var* '(("foo" . "bar") ("baz" . "qux"))) *VAR* CL-USER> (xf #'reverse (cdr (second *var*))) "xuq" CL-USER> *var* (("foo" . "bar") ("baz" . "xuq")) 

extensions:

 (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 helpful.

This answer is based on Paul Graham's On Lisp , section 12.4 More Complex Utilities.

+6
source share

Using define-modify-macro (plus bit)

Marking the answer provides a complete way to do this from scratch, but in fact it can be approximated using define-modify-macro and done using define-modify-macro plus another macro:

 (define-modify-macro xxf (function) ; like XF, but the place comes first, then the function (lambda (value function) (funcall function value))) (let ((l (copy-tree '(("foo" . "bar") ("baz" . "qux"))))) (xxf (cdr (second l)) #'reverse) l) ;=> (("foo" . "bar") ("baz" . "xuq")) 

To reverse the order, it is easy to define an xf macro that expands to an xxf call:

 (defmacro xf (function place) `(xxf ,place ,function)) (let ((l (copy-tree '(("foo" . "bar") ("baz" . "qux"))))) (xf #'reverse (cdr (second l))) l) ;=> (("foo" . "bar") ("baz" . "xuq")) 

Quality improvement

The current version accepts only one function as an argument. Many functions accept additional arguments (for example, additional required arguments, keyword arguments, and optional arguments). We can still handle those with define-modify-macro , though:

 (define-modify-macro xxf (function &rest args) (lambda (value function &rest args) (apply function value args))) (defmacro xf (function place &rest args) `(xxf ,place ,function ,@args)) 

 (let ((l (copy-tree '("HeLlo WoRlD" "HeLlo WoRlD")))) (xf #'remove-duplicates (first l) :test #'char=) (xf #'remove-duplicates (second l) :test #'char-equal) l) ;=> ("HeL WoRlD" "He WoRlD") 
+6
source share

All Articles