如何用任意功能修改位置 [英] How to modify place with arbitrary function
问题描述
有时我们需要修改地方,但是这里没有满足我们需求的内置功能.</p>
例如,这里是incf
和decf
用于加法和减法:
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
但是乘法和除法呢?如果我们希望用任意功能修改一个地方,就像这样:
(xf (lambda (x) ...) *x*)
xf
实用程序将非常有用,尤其是当我们必须处理深度嵌套的结构时:
(my-accessor (aref (cdr *my-data*) n))
使用define-modify-macro
定义新的宏
定义新的方便使用的宏的一种简单方法是 define-modify-macro
.这是一个方便的宏,可以为我们创建其他宏.
语法:
定义-修改-宏名称lambda列表功能[文档]
⇒名称
我们应该提供新宏的名称,参数列表(不包括此处的位置)以及将用于处理的功能符号.
使用示例:
(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")
但是,define-modify-macro
不能用于任意处理.在这里,我们必须看看其他可能性.
功能get-setf-expansion
函数 get-setf-expansion
不会创建任何宏,但是提供了我们可以提供的信息用来写我们自己的.
语法:
获取设置扩展位置和可选环境
⇒vars,vals,store-vars,writer-form,reader-form
如您所见,它返回了一堆值,因此乍一看可能会造成混淆.让我们以示例为例:
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
编写xf
宏
似乎现在我们已经掌握了编写xf
宏的所有信息:
(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
请注意,xf
宏采用环境变量并将其传递给get-setf-expansion
.需要使用此变量,以确保考虑到在编译环境中建立的任何词法绑定或定义.
让我们尝试一下:
CL-USER> (defvar *var* '(("foo" . "bar") ("baz" . "qux")))
*VAR*
CL-USER> (xf #'reverse (cdr (second *var*)))
"xuq"
CL-USER> *var*
(("foo" . "bar") ("baz" . "xuq"))
扩展:
(LET* ((#:G6033 #'REVERSE)
(#:TEMP-6032 (SECOND *VAR*))
(#:NEW-6031 (FUNCALL #:G6033
(CDR #:TEMP-6032))))
(SYSTEM::%RPLACD #:TEMP-6032 #:NEW-6031))
我希望这些信息有用.
此答案基于 Paul Graham's Lisp ,部分 12.4更多复杂的实用程序.
Sometimes we need to modify a place but here is no built-in function that meets our needs.
For instance, here are 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 how about multiplication and division? What if we wish to modify a place with arbitrary function, like this:
(xf (lambda (x) ...) *x*)
xf
utility would be very useful, especially when we have to deal with deeply nested structures:
(my-accessor (aref (cdr *my-data*) n))
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.
这篇关于如何用任意功能修改位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!