访问整数位的函数的 Common Lisp setf 扩展

Common Lisp setf expansions for functions which access bits of integers

我正在用 Common Lisp 编写一个程序,它需要为数组中的大量条目存储一堆状态位(整个程序几乎是 fortran-in-lisp)并且状态位是编码为位于此数组中的 fixnum 中的位。这些状态位的访问器实际上将由宏定义,因此我不必关心分配这些位,但示例 reader 函数可能是

(defun deadp (e)
  (logbitp 0 e))

(在现实生活中,这将被内联并与声明混在一起以尝试确保它是快速的,但我认为这些在这里并不重要。)

我需要这些东西是函数,因为我希望能够映射它们,但也因为使用宏到内联函数的事情让我感觉很糟糕。

然后我会像这样使用它:

(defconstant status-index 3)
...
(dotimes (i nentries)
  (unless (deadp (aref entries i status-index))
    ...))

(在现实生活中 (aref entries i status-index) 将是 (status entries i),这又需要一个 setf 方法,但我认为这很容易。)

(loop for i below nentries
      counting (if (deadp entries i status-index) 1 0))

当然还有其他类似的单位标志,它们有不同的位与之关联。

所以,现在我希望能够做到这一点:

(dotimes (i nentries)
  ...
  (when ...
    (setf (deadp (aref entries i status-index) t)))
  ...)

应该变成等同于

的代码
(dotimes (i nentries)
  ...
  (when ...
    (progn 
      (setf (ldb (byte 1 0) (aref entries i status-index)) 1)
      t))
  ...)

还有这个:

(let ((status 0))
  ...
  (when ...
    (setf (deadp status) t))
  ...)

这应该变成与此等效的代码:

(let ((status 0))
  ...
  (when ...
    (progn
      (setf (ldb (byte 1 0) status) 1)
      t))
  ...)

换句话说,我希望我的 deadp 函数成为一个访问器,并让 setf 以一般方式工作:(setf (deadp (cdr x)) nil) 应该可以工作,等等

因此,这让我陷入了我长期以来一直避免的 CL 的部分:定义 setf 扩展器。很明显,仅仅定义一个 (setf deadp) 函数是行不通的,因为数字是不可变的,而且我 相当 确定 defsetf 不够强大,所以我需要define-setf-expander,我不明白。

有人可以解释一下我需要怎么做吗?我认为特定的 deadp 函数并不重要,尽管我关心的所有函数看起来都像是它的变体。


另一种答案是“这是一种脑死亡的方法,而不是……”,我对此持开放态度。我考虑过编写将数组抽象出来的代码,所以我会写 (deadp people ...) 而不是 (deadp (aref ...)),其中 people 是人员数组。这很好,而且很容易看出如何使 setf 可用,除了我还希望能够说出 (deadp status),其中 status 只是一个定数。但也许有更好的方法。

根据 GET-SETF-EXPANSION 的 SBCL 文档,setf 扩展器必须: "Return SETF 机制需要的五个值:临时列表 变量,用于填充它们的值列表,临时列表 用于新值、设置函数和访问函数。” setting function和accessing function实际上只是在地方设置和访问值的形式,而不是函数对象。

试试这个:

(define-setf-expander deadp (place)
  (let ((new (gensym)))
    (values nil nil (list new)
            `(progn (setf (ldb (byte 1 0) ,place) (if ,new 1 0))
                    ,new)
            `(deadp ,place))))

样本扩展:

(let ((status 1))
  (setf (deadp status) t))
->
(let ((status 1))
  (LET* ((#:G605 T))
    (SETF (LDB (BYTE 1 0) STATUS)
            (IF #:G605
                1
                0))
    #:G605))