如何用任意功能修改位置 [英] How to modify place with arbitrary function

查看:127
本文介绍了如何用任意功能修改位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有时我们需要修改地方,但是这里没有满足我们需求的内置功能.<​​/p>

例如,这里是incfdecf用于加法和减法:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆