在宏中定义 class 和方法
Defining class and methods in macro
我对 Common Lisp 宏还是很陌生。
对于带有 defgeneric 的 defclass 的抽象,我认为制作一个宏会很好。
一个完全天真的实现看起来像:
(defmacro defgserver (name &key call-handler cast-handler)
"TODO: needs firther testing. Convenience macro to more easily create a new `gserver' class."
`(progn
(defclass ,name (gserver) ())
(defmethod handle-call ((server ,name) message current-state)
,(if call-handler call-handler nil))
(defmethod handle-cast ((server ,name) message current-state)
,(if cast-handler cast-handler nil))))
使用时,错误提示 'message' 未知。
我不确定。 'message'是defgeneric
的一个参数名:
(defgeneric handle-call (gserver message current-state))
使用宏我看到警告 'undefined variable message':
(defgserver foo :call-handler
(progn
(print message)))
; in: DEFGSERVER FOO
; (PRINT MESSAGE)
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::MESSAGE
使用时会产生以下后果:
CL-USER> (defvar *my* (make-instance 'foo))
*MY*
CL-USER> (call *my* "Foo")
<WARN> [10:55:10] cl-gserver gserver.lisp (handle-message fun5) -
Error condition was raised on message processing: CL-GSERVER::C: #<UNBOUND-VARIABLE MESSAGE {1002E24553}>
因此 message
and/or current-state
必须要发生一些事情。
是否应该将它们嵌入到使用宏的当前包中?
曼弗雷德
如前所述,问题是您在谈论不同的符号。
然而,这实际上是一个更普遍问题的征兆:您正在尝试做的是一种 照应。如果您修复了包结构,那么这有效:
(defgserver foo :call-handler
(progn
(print message)))
那么,message
到底是什么?它来自哪里,该范围内存在哪些 other 绑定?指代词可能很有用,但它也可能是像这样的隐蔽错误的来源。
所以,我认为一个更好的方法可以避免这个问题,即 *-handler
选项应该指定他们期望的参数。所以你应该写这样的东西而不是上面的形式:
(defgserver foo
:call-handler ((server message state)
(print message)
(detonate server)))
所以在这里,:call-handler-option
的值是函数的参数列表和主体,宏将把它变成一个专门处理第一个参数的方法。因为它创建的方法有宏用户提供的参数列表,所以名称从来没有问题,也没有照应。
因此,一种方法是做两件事:
- 使这些选项的默认值适合处理成没有任何特殊外壳的方法;
- 在宏中编写一个小的本地函数,将这些规范之一转换为合适的
(defmethod ...)
形式。
第二部分当然是可选的,但它节省了一点代码。
除此之外,我还做了一个有点肮脏的把戏:我改变了宏定义,所以它有一个 &body
选项,它的值被忽略了。我这样做的唯一原因是帮助我的编辑更好地缩进它。
所以,这是一个修订版:
(defmacro defgserver (name &body forms &key
(call-handler '((server message current-state)
(declare (ignorable
server message current-state))
nil))
(cast-handler '((server message current-state)
(declare (ignorable
server message current-state))
nil)))
"TODO: needs firther testing. Convenience macro to more easily
create a new `gserver' class."
(declare (ignorable forms))
(flet ((write-method (mname mform)
(destructuring-bind (args &body decls/forms) mform
`(defmethod ,mname ((,(first args) ,name) ,@(rest args))
,@decls/forms))))
`(progn
(defclass ,name (gserver) ())
,(write-method 'handle-call call-handler)
,(write-method 'handle-cast cast-handler))))
现在
(defgserver foo
:call-handler ((server message state)
(print message)
(detonate server)))
扩展到
(progn
(defclass foo (gserver) nil)
(defmethod handle-call ((server foo) message state)
(print message)
(detonate server))
(defmethod handle-cast ((server foo) message current-state)
(declare (ignorable server message current-state))
nil))
我对 Common Lisp 宏还是很陌生。
对于带有 defgeneric 的 defclass 的抽象,我认为制作一个宏会很好。
一个完全天真的实现看起来像:
(defmacro defgserver (name &key call-handler cast-handler)
"TODO: needs firther testing. Convenience macro to more easily create a new `gserver' class."
`(progn
(defclass ,name (gserver) ())
(defmethod handle-call ((server ,name) message current-state)
,(if call-handler call-handler nil))
(defmethod handle-cast ((server ,name) message current-state)
,(if cast-handler cast-handler nil))))
使用时,错误提示 'message' 未知。
我不确定。 'message'是defgeneric
的一个参数名:
(defgeneric handle-call (gserver message current-state))
使用宏我看到警告 'undefined variable message':
(defgserver foo :call-handler
(progn
(print message)))
; in: DEFGSERVER FOO
; (PRINT MESSAGE)
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::MESSAGE
使用时会产生以下后果:
CL-USER> (defvar *my* (make-instance 'foo))
*MY*
CL-USER> (call *my* "Foo")
<WARN> [10:55:10] cl-gserver gserver.lisp (handle-message fun5) -
Error condition was raised on message processing: CL-GSERVER::C: #<UNBOUND-VARIABLE MESSAGE {1002E24553}>
因此 message
and/or current-state
必须要发生一些事情。
是否应该将它们嵌入到使用宏的当前包中?
曼弗雷德
如前所述,问题是您在谈论不同的符号。
然而,这实际上是一个更普遍问题的征兆:您正在尝试做的是一种 照应。如果您修复了包结构,那么这有效:
(defgserver foo :call-handler
(progn
(print message)))
那么,message
到底是什么?它来自哪里,该范围内存在哪些 other 绑定?指代词可能很有用,但它也可能是像这样的隐蔽错误的来源。
所以,我认为一个更好的方法可以避免这个问题,即 *-handler
选项应该指定他们期望的参数。所以你应该写这样的东西而不是上面的形式:
(defgserver foo
:call-handler ((server message state)
(print message)
(detonate server)))
所以在这里,:call-handler-option
的值是函数的参数列表和主体,宏将把它变成一个专门处理第一个参数的方法。因为它创建的方法有宏用户提供的参数列表,所以名称从来没有问题,也没有照应。
因此,一种方法是做两件事:
- 使这些选项的默认值适合处理成没有任何特殊外壳的方法;
- 在宏中编写一个小的本地函数,将这些规范之一转换为合适的
(defmethod ...)
形式。
第二部分当然是可选的,但它节省了一点代码。
除此之外,我还做了一个有点肮脏的把戏:我改变了宏定义,所以它有一个 &body
选项,它的值被忽略了。我这样做的唯一原因是帮助我的编辑更好地缩进它。
所以,这是一个修订版:
(defmacro defgserver (name &body forms &key
(call-handler '((server message current-state)
(declare (ignorable
server message current-state))
nil))
(cast-handler '((server message current-state)
(declare (ignorable
server message current-state))
nil)))
"TODO: needs firther testing. Convenience macro to more easily
create a new `gserver' class."
(declare (ignorable forms))
(flet ((write-method (mname mform)
(destructuring-bind (args &body decls/forms) mform
`(defmethod ,mname ((,(first args) ,name) ,@(rest args))
,@decls/forms))))
`(progn
(defclass ,name (gserver) ())
,(write-method 'handle-call call-handler)
,(write-method 'handle-cast cast-handler))))
现在
(defgserver foo
:call-handler ((server message state)
(print message)
(detonate server)))
扩展到
(progn
(defclass foo (gserver) nil)
(defmethod handle-call ((server foo) message state)
(print message)
(detonate server))
(defmethod handle-cast ((server foo) message current-state)
(declare (ignorable server message current-state))
nil))