Reputation: 517
I'm still quite new to Common Lisp macros.
For an abstraction over a defclass with defgeneric I thought it'd be nice to make a macro.
A complitely naive implementation looks like:
(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))))
When used the error says that 'message' is not known.
I'm not sure. 'message' is the name of a parameter of defgeneric
:
(defgeneric handle-call (gserver message current-state))
Using the macro I see a warning 'undefined variable message':
(defgserver foo :call-handler
(progn
(print message)))
; in: DEFGSERVER FOO
; (PRINT MESSAGE)
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::MESSAGE
Which when used has this consequence:
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}>
So something has to happen with message
and/or current-state
.
Should they be interned into the current package where the macro is used?
Manfred
Upvotes: 1
Views: 239
Reputation:
The problem, as mentioned, is that you are talking about different symbols.
However this is really a symptom of a more general problem: what you are trying to do is a sort of anaphora. If you fixed up the package structure so this worked:
(defgserver foo :call-handler
(progn
(print message)))
Then, well, what exactly is message
? Where did it come from, what other bindings exist in that scope? Anaphora can be useful, but it also can be a source of obscure bugs like this.
So, I think a better way to do this, which avoids this problem is to say that the *-handler
options should specify what arguments they expect. So instead of the above form you'd write something like this:
(defgserver foo
:call-handler ((server message state)
(print message)
(detonate server)))
So here, value of the :call-handler-option
is the argument list and body of a function, which the macro will turn into a method specialising on the first argument. Because the methods it creates have argument lists provided by the user of the macro there's never a problem with names, and there is no anaphora.
So, one way to do that is to do two things:
(defmethod ...)
form.The second part is optional of course, but it saves a little bit of code.
In addition to this I've also done a slightly dirty trick: I've changed the macro definition so it has an &body
option, the value of which is ignored. The only reason I've done this is to help my editor indent it better.
So, here's a revised version:
(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))))
And now
(defgserver foo
:call-handler ((server message state)
(print message)
(detonate server)))
Expands to
(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))
Upvotes: 2