【问题标题】:Dynamically defining setf expanders动态定义 setf 扩展器
【发布时间】:2017-07-19 21:22:45
【问题描述】:

我正在尝试定义一个宏,它将采用结构的名称、键和结构中的哈希表的名称,并定义函数来访问和修改哈希中键下的值。

(defmacro make-hash-accessor (struct-name key hash)
  (let ((key-accessor  (gensym))
        (hash-accessor (gensym)))
    `(let ((,key-accessor  (accessor-name ,struct-name ,key))
           (,hash-accessor (accessor-name ,struct-name ,hash)))
       (setf (fdefinition ,key-accessor) ; reads
             (lambda (instance)
               (gethash ',key
                (funcall ,hash-accessor instance))))
       (setf (fdefinition '(setf ,key-accessor)) ; modifies
             (lambda (instance to-value)
               (setf (gethash ',key
                      (funcall ,hash-accessor instance))
                 to-value))))))

;; Returns the symbol that would be the name of an accessor for a struct's slot
(defmacro accessor-name (struct-name slot)
  `(intern
    (concatenate 'string (symbol-name ',struct-name) "-" (symbol-name ',slot))))

为了测试这个我有:

(defstruct tester
  (hash (make-hash-table)))

(defvar too (make-tester))
(setf (gethash 'x (tester-hash too)) 3)

当我跑步时

(make-hash-accessor tester x hash)

然后

(tester-x too)

它应该返回3 T,但是

(setf (tester-x too) 5)

给出错误:

The function (COMMON-LISP:SETF COMMON-LISP-USER::TESTER-X) is undefined.
   [Condition of type UNDEFINED-FUNCTION]

(macroexpand-1 '(make-hash-accessor tester x hash)) 扩展为

(LET ((#:G690 (ACCESSOR-NAME TESTER X)) (#:G691 (ACCESSOR-NAME TESTER HASH)))
  (SETF (FDEFINITION #:G690)
        (LAMBDA (INSTANCE) (GETHASH 'X (FUNCALL #:G691 INSTANCE))))
  (SETF (FDEFINITION '(SETF #:G690))
        (LAMBDA (INSTANCE TO-VALUE)
          (SETF (GETHASH 'X (FUNCALL #:G691 INSTANCE)) TO-VALUE))))
T

我正在使用 SBCL。我做错了什么?

【问题讨论】:

    标签: macros common-lisp symbols dynamically-generated setf


    【解决方案1】:

    您应该尽可能使用defun。 具体来说,这里用 defmacro 代替 accessor-name 代替 (setf fdefinition) 代替您的访问器:

    (defmacro define-hash-accessor (struct-name key hash)
      (flet ((concat-symbols (s1 s2)
               (intern (concatenate 'string (symbol-name s1) "-" (symbol-name s2)))))
        (let ((hash-key (concat-symbols struct-name key))
              (get-hash (concat-symbols struct-name hash)))
          `(progn
             (defun ,hash-key (instance)
               (gethash ',key (,get-hash instance)))
             (defun (setf ,hash-key) (to-value instance)
               (setf (gethash ',key (,get-hash instance)) to-value))
             ',hash-key))))
    (defstruct tester
      (hash (make-hash-table)))
    (defvar too (make-tester))
    (setf (gethash 'x (tester-hash too)) 3)
    too
    ==> #S(TESTER :HASH #S(HASH-TABLE :TEST FASTHASH-EQL (X . 3)))
    (define-hash-accessor tester x hash)
    ==> tester-x
    (tester-x too)
    ==> 7; T
    (setf (tester-x too) 5)
    too
    ==> #S(TESTER :HASH #S(HASH-TABLE :TEST FASTHASH-EQL (X . 5)))
    

    请注意,我为宏使用了一个更传统的名称:因为它定义访问器,所以通常将其命名为define-...(参见define-conditiondefpackage)。 make-... 通常用于函数返回对象(参见make-package)。

    另见Is defun or setf preferred for creating function definitions in common lisp and why? 请记住,样式很重要,无论是缩进还是命名变量、函数和宏。

    【讨论】:

    • 将宏命名为DEFINE-HASH-ACCESSOR 可能更清楚,因为它定义了函数,而不是返回它们。也许也可以将ACCESSOR-NAME 移动到一个本地函数中,这样就不必担心它在编译时是否可用。
    • @jkiiski:你说得对,我试图保留 OP 的符号,但我会编辑。
    猜你喜欢
    • 2012-07-13
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 2010-10-11
    • 2020-03-03
    • 2012-07-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多