有对为 generalized variable 定义 setter method 了解的小伙伴吗?

有一个简单的需求:为普通变量定义一个 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--defsettergv--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-letplacegv 参数中获取到了 gettersetter 表达式,从而能够正常工作。

1 个赞

(再补充下吧,虽然感觉有点啰嗦了)

更准确点说,gv-define-setter 只能用在具有 setter 函数的名字上,car 对应 setcargethash 对应 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 个赞