有一个简单的需求:为普通变量定义一个 setter method
。
普通变量是指那些保存的是一个数字的变量,非列表或者向量。
需求是这样的:
我希望为 cl-ldb(类似 Common Lisp 中 ldb 的一个函数) 定义一个 setter method
。
我用 gv-define-setter
尝试了一下
(gv-define-setter cl-ldb (value bytespec integer)
`(setq ,integer (cl-dpb ,value ,bytespec ,integer)))
(defvar *num* 0 )
(setf (cl-ldb (cl-byte 8 0) *num*) 128)
*num* ;;=> 0,可以看到:*num* 的值并没有改变,依旧是 0,期望值是 128
;;; 将上面的表达式展开 =>
(let* ((v (cl-byte 8 0))
(v *num*))
(setq v (cl-dpb 128 v v)))
;;; 结果发现生成的代码中 *num* 是被遮盖掉的,并没有被赋予新值。
不知道有没有方法定义出不被遮盖掉的setter method
(使用非废弃的包里的函数或者宏)?
define-setf-expander
倒是可以很方便的定义出来,不过这个宏位于 cl.el
包中,而这个包已经被废弃了:
(require 'cl)
(define-setf-expander cl-ldb (bytespec integer)
(let ((setvar (gensym)))
(values
`()
`()
`(,setvar)
`(progn (setq ,integer (cl-dpb ,setvar ,bytespec ,integer)) ,setvar)
`(cl-ldb ,bytespec ,integer))))
(defvar *num* 0 )
(setf (cl-ldb (cl-byte 8 0) *num*) 128)
*num* ;;;=> 得到了期望的结果:128
;;;将上面的表达式展开 =>
(progn
(setq *num* (cl-dpb 128 (cl-byte 8 0) *num*)) 128)
几个上面用到的函数:
(defun cl-byte (size position)
(cons size position))
(defun cl-ldb (bytespec integer)
(logand (ash integer (- (cdr bytespec)))
(- (expt 2 (car bytespec)) 1)))
(defun cl-dpb (newbyte bytespec integer)
(let ((mask (- (expt 2 (car bytespec)) 1)))
(logior (ash (logand mask newbyte) (cdr bytespec))
(logand integer (lognot (ash mask (cdr bytespec)))))))
不知道我的意思有没有表达出来,就是:
定义一个变量 *num*
,通过 setf
更改 *num*
变量上的连续位,而这个改变最终是有反应到 *num*
本身的:
(defvar *num* 0 )
;;; 将 *num* 的 0~7 位用 128 代替
(setf (cl-ldb (cl-byte 8 0) *num*) 128)
;; *num*=>128
;;; 将 *num* 的 8~15 位用 255 代替
(setf (cl-ldb (cl-byte 8 8) *num*) 255)
;; *num*=>65408
可以用复杂一点的 gv-define-expander
(gv-define-expander cl-ldb
(lambda (do bytespec place)
(gv-letplace (getter setter) place
(funcall do
`(cl-ldb ,bytespec ,getter)
(lambda (v)
`(progn
,(funcall setter `(cl-dpb ,v ,bytespec ,getter))
,v))))))
gv-define-setter
的话,setter类似于一个function,integer会被求值,所以setq没有效果。
2 个赞
gv-define-setter
只能用来定义基础的 setter 函数,类似于 car/setcar
, aref/aset
之类的。如果 name
对应的 getter 函数接受了 gv
参数它是不能正常工作的。
具体原因可见源代码:gv-define-setter
内部调用了 gv--defsetter
,gv--defsetter
展开过程中会使用 macroexp-let2
来保证 setter
函数将要应用到的参数们只被求值一次。
(defun gv--defsetter (name setter do args &optional vars)
"Helper function used by code generated by `gv-define-setter'.
NAME is the name of the getter function.
SETTER is a function that generates the code for the setter.
NAME accept ARGS as arguments and SETTER accepts (NEWVAL . ARGS).
VARS is used internally for recursive calls."
(if (null args)
(let ((vars (nreverse vars)))
(funcall do `(,name ,@vars) (lambda (v) (apply setter v vars))))
;; FIXME: Often it would be OK to skip this `let', but in general,
;; `do' may have all kinds of side-effects.
(macroexp-let2 nil v (car args)
(gv--defsetter name setter do (cdr args) (cons v vars)))))
这样,参数表里面的 gv
参数的值就直接保存到了局部变量中,就不知道它对应的 setter
函数相关信息了。(你的代码中的 gv
变量就是 integer
,它的 setter form 是 (setq integer …),但是这个没法得到。
上面的解决方案中使用了 gv-letplace
从 gv
参数中获取到了 getter
和 setter
表达式,从而能够正常工作。
1 个赞
(再补充下吧,虽然感觉有点啰嗦了)
更准确点说,gv-define-setter
只能用在具有 setter
函数的名字上,car
对应 setcar
,gethash
对应 puthash
,等等。在 gv.el
中定义了基本 getter 函数的 setf
form,这里截一段:
(gv-define-simple-setter aref aset)
(gv-define-simple-setter char-table-range set-char-table-range)
(gv-define-simple-setter car setcar)
(gv-define-simple-setter cdr setcdr)
;; FIXME: add compiler-macros for `cXXr' instead!
(gv-define-setter caar (val x) `(setcar (car ,x) ,val))
(gv-define-setter cadr (val x) `(setcar (cdr ,x) ,val))
(gv-define-setter cdar (val x) `(setcdr (car ,x) ,val))
(gv-define-setter cddr (val x) `(setcdr (cdr ,x) ,val))
(gv-define-setter elt (store seq n)
`(if (listp ,seq) (setcar (nthcdr ,n ,seq) ,store)
(aset ,seq ,n ,store)))
(gv-define-simple-setter get put)
(gv-define-setter gethash (val k h &optional _d) `(puthash ,k ,val ,h))
符号是没有 setter 函数的,setq
只是个 special form。
1 个赞
不啰嗦不啰嗦,我挺喜欢看的,辛苦你们了,你知乎上的文章我也看了,收获挺多的,非常感谢……
1 个赞