为什么这个符号扩展在 Common Lisp 中格式错误?

发布于 2025-01-12 07:13:22 字数 1930 浏览 0 评论 0原文

我正在尝试使用 SBCL 和 Slime 在这个关于 CLOS 的 教程 上进行练习( Emacs)。

我有这个类、实例和函数来设置插槽的值:

(defclass point ()
  (x y z))

(defvar my-point
  (make-instance 'point))

(defun with-slots-set-point-values (point a b c)
  (with-slots (x y z) point (setf x a y b z c)))

使用 REPL,它工作正常:

CL-USER> (with-slots-set-point-values my-point 111 222 333)

333

CL-USER> (describe my-point)
#<POINT {1003747793}>
  [standard-object]

Slots with :INSTANCE allocation:
  X                              = 111
  Y                              = 222
  Z                              = 333
; No value

现在,练习表明使用 symbol-macrolet 我需要实现我的 版本代码>带槽。

我有我的 with-slots 的部分实现(我仍然需要插入添加操作):

(defun partial-my-with-slots (slot-list object)
   (mapcar #'(lambda (alpha beta) (list alpha beta))
           slot-list
           (mapcar #'(lambda (var) (slot-value object var)) slot-list)))

调用它时它可以工作:

CL-USER> (partial-my-with-slots '(x y z) my-point)
((X 111) (Y 222) (Z 333))

因为符号宏的这种使用有效:

CL-USER> (symbol-macrolet ((x 111) (y 222) (z 333))
   (+ x y z))
666

我尝试这样做:

CL-USER> (symbol-macrolet (partial-my-with-slots '(x y z) my-point)
   (+ x y z))

但是,对于由于某种我不知道的原因,史莱姆抛出错误:

malformed symbol/expansion pair: PARTIAL-MY-WITH-SLOTS
   [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]

为什么会发生这种情况?我该如何解决这个问题?

I am trying to do the exercises on this tutorial about CLOS using SBCL and Slime (Emacs).

I have this class, instance, and function to set values for the slots:

(defclass point ()
  (x y z))

(defvar my-point
  (make-instance 'point))

(defun with-slots-set-point-values (point a b c)
  (with-slots (x y z) point (setf x a y b z c)))

Using the REPL, it works fine:

CL-USER> (with-slots-set-point-values my-point 111 222 333)

333

CL-USER> (describe my-point)
#<POINT {1003747793}>
  [standard-object]

Slots with :INSTANCE allocation:
  X                              = 111
  Y                              = 222
  Z                              = 333
; No value

Now, the exercises indicates that using the symbol-macrolet I need to implement my version of with-slots.

I have a partial implementation of my with-slots (I still need to insert add the operation):

(defun partial-my-with-slots (slot-list object)
   (mapcar #'(lambda (alpha beta) (list alpha beta))
           slot-list
           (mapcar #'(lambda (var) (slot-value object var)) slot-list)))

It works when calling it:

CL-USER> (partial-my-with-slots '(x y z) my-point)
((X 111) (Y 222) (Z 333))

Since this use of symbol-macrolet works:

CL-USER> (symbol-macrolet ((x 111) (y 222) (z 333))
   (+ x y z))
666

I tried doing:

CL-USER> (symbol-macrolet (partial-my-with-slots '(x y z) my-point)
   (+ x y z))

But, for some reason that I do not know, Slime throws the error:

malformed symbol/expansion pair: PARTIAL-MY-WITH-SLOTS
   [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]

Why does this happen? How can I fix this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

美人迟暮 2025-01-19 07:13:22

您不能将 with-slots 编写为在运行时调用的函数。相反,它需要是一个将源代码作为参数并返回其他源代码的函数。特别是如果给定这个参数,

(my-with-slots (x ...) <something> <form> ...)

它应该返回这个结果:

(let ((<invisible-variable> <something))
  (symbol-macrolet ((x (slot-value <invisible-variable>)) ...)
    <form> ...))

您需要 因此您只需评估 一次。

好吧,这里有一个函数可以完成大部分工作:

(defun mws-expander (form)
  (destructuring-bind (mws (&rest slot-names) object-form &rest forms) form
    (declare (ignore mws))
    `(let ((<invisible-variable> ,object-form))
       (symbol-macrolet ,(mapcar (lambda (slot-name)
                                    `(,slot-name (slot-value <invisible-variable> 
                                                             ',slot-name)))
                                  slot-names)
         ,@forms))))

你可以检查一下:

> (mws-expander '(my-with-slots (x y) a (list x y)))
(let ((<invisible-variable> a))
  (symbol-macrolet ((x (slot-value <invisible-variable> 'x))
                    (y (slot-value <invisible-variable> 'y)))
    (list x y)))

所以这几乎是正确的,除了不可见变量确实需要不可见:

(defun mws-expander (form)
  (destructuring-bind (mws (&rest slot-names) object-form &rest forms) form
    (declare (ignore mws))
    (let ((<invisible-variable> (gensym)))
      `(let ((,<invisible-variable> ,object-form))
         (symbol-macrolet ,(mapcar (lambda (slot-name)
                                     `(,slot-name (slot-value ,<invisible-variable>
                                                              ',slot-name)))
                                   slot-names)
         ,@forms)))))

现在:

> (mws-expander '(my-with-slots (x y) a (list x y)))
(let ((#:g1509 a))
  (symbol-macrolet ((x (slot-value #:g1509 'x))
                    (y (slot-value #:g1509 'y)))
    (list x y)))

好吧,一个函数将源代码作为参数并返回其他值源代码是一个宏。因此,最后,我们需要将此函数安装为宏扩展器,并安排忽略宏函数获得的第二个参数:

(setf (macro-function 'mws)
      (lambda (form environment)
        (declare (ignore environment))
        (mws-expander form)))

现在:

> (macroexpand '(mws (x y) a (list x y)))
(let ((#:g1434 a))
  (symbol-macrolet ((x (slot-value #:g1434 'x)) (y (slot-value #:g1434 'y)))
    (list x y)))

当然,这将更传统地使用 defmacro 编写:

(defmacro mws ((&rest slot-names) object-form &rest forms)
  (let ((<invisible-variable> (gensym)))
    `(let ((,<invisible-variable> ,object-form))
       (symbol-macrolet ,(mapcar (lambda (slot-name)
                                   `(,slot-name (slot-value ,<invisible-variable> ',slot-name)))
                                 slot-names)
         ,@forms))))

但是这两个定义是等效的(模数需要一些eval-whenery才能使第一个与编译器一起正常工作)。

You can't write with-slots as a function which is called at run time. Instead it needs to be a function which takes source code as an argument and returns other source code. In particular if given this argument

(my-with-slots (x ...) <something> <form> ...)

It should return this result:

(let ((<invisible-variable> <something))
  (symbol-macrolet ((x (slot-value <invisible-variable>)) ...)
    <form> ...))

You need <invisible-variable> so you evaluate <object-form> only once.

Well, here is a function which does most of that:

(defun mws-expander (form)
  (destructuring-bind (mws (&rest slot-names) object-form &rest forms) form
    (declare (ignore mws))
    `(let ((<invisible-variable> ,object-form))
       (symbol-macrolet ,(mapcar (lambda (slot-name)
                                    `(,slot-name (slot-value <invisible-variable> 
                                                             ',slot-name)))
                                  slot-names)
         ,@forms))))

And you can check this:

> (mws-expander '(my-with-slots (x y) a (list x y)))
(let ((<invisible-variable> a))
  (symbol-macrolet ((x (slot-value <invisible-variable> 'x))
                    (y (slot-value <invisible-variable> 'y)))
    (list x y)))

So that's almost right, except the invisible variable really needs to be invisible:

(defun mws-expander (form)
  (destructuring-bind (mws (&rest slot-names) object-form &rest forms) form
    (declare (ignore mws))
    (let ((<invisible-variable> (gensym)))
      `(let ((,<invisible-variable> ,object-form))
         (symbol-macrolet ,(mapcar (lambda (slot-name)
                                     `(,slot-name (slot-value ,<invisible-variable>
                                                              ',slot-name)))
                                   slot-names)
         ,@forms)))))

And now:

> (mws-expander '(my-with-slots (x y) a (list x y)))
(let ((#:g1509 a))
  (symbol-macrolet ((x (slot-value #:g1509 'x))
                    (y (slot-value #:g1509 'y)))
    (list x y)))

Well, a function which takes source code as an argument and returns other source code is a macro. So, finally, we need to install this function as a macroexpander, arranging to ignore the second argument that macro functions get:

(setf (macro-function 'mws)
      (lambda (form environment)
        (declare (ignore environment))
        (mws-expander form)))

And now:

> (macroexpand '(mws (x y) a (list x y)))
(let ((#:g1434 a))
  (symbol-macrolet ((x (slot-value #:g1434 'x)) (y (slot-value #:g1434 'y)))
    (list x y)))

This would be more conventionally written using defmacro, of course:

(defmacro mws ((&rest slot-names) object-form &rest forms)
  (let ((<invisible-variable> (gensym)))
    `(let ((,<invisible-variable> ,object-form))
       (symbol-macrolet ,(mapcar (lambda (slot-name)
                                   `(,slot-name (slot-value ,<invisible-variable> ',slot-name)))
                                 slot-names)
         ,@forms))))

However the two definitions are equivalent (modulo needing some eval-whenery to make the first work properly with the compiler).

箹锭⒈辈孓 2025-01-19 07:13:22

您需要返回在代入宏扩展时调用slot-value 的表达式,而不是立即调用该函数。反引号对此很有用。

(defun partial-my-with-slots (slot-list object)
   (mapcar #'(lambda (alpha beta) (list alpha beta))
           slot-list
           (mapcar #'(lambda (var) `(slot-value ,object ',var)) slot-list)))
> (partial-my-with-slots '(x y z) 'my-point)
((x (slot-value my-point 'x)) (y (slot-value my-point 'y)) (z (slot-value my-point 'z)))

您可以在 with-slots 宏中使用它,如下所示:

(defmacro my-with-slots ((&rest slot-names) instance-form &body body)
  `(symbol-macrolet ,(partial-my-with-slots slot-names instance-form)
     ,@body))

> (macroexpand '(my-with-slots (x y z) point (setf x a y b z c))) 
(SYMBOL-MACROLET ((X (SLOT-VALUE POINT 'X))
                  (Y (SLOT-VALUE POINT 'Y))
                  (Z (SLOT-VALUE POINT 'Z)))
  (SETF X A
        Y B
        Z C))

You need to return expressions that will call slot-value when substituted into the macro expansion, rather than calling the function immediately. Backquote is useful for this.

(defun partial-my-with-slots (slot-list object)
   (mapcar #'(lambda (alpha beta) (list alpha beta))
           slot-list
           (mapcar #'(lambda (var) `(slot-value ,object ',var)) slot-list)))
> (partial-my-with-slots '(x y z) 'my-point)
((x (slot-value my-point 'x)) (y (slot-value my-point 'y)) (z (slot-value my-point 'z)))

You use this in your with-slots macro like this:

(defmacro my-with-slots ((&rest slot-names) instance-form &body body)
  `(symbol-macrolet ,(partial-my-with-slots slot-names instance-form)
     ,@body))

> (macroexpand '(my-with-slots (x y z) point (setf x a y b z c))) 
(SYMBOL-MACROLET ((X (SLOT-VALUE POINT 'X))
                  (Y (SLOT-VALUE POINT 'Y))
                  (Z (SLOT-VALUE POINT 'Z)))
  (SETF X A
        Y B
        Z C))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文