I've been doing some network programming this weekend and I've hit an
issue on which I'd like some suggestions.
I send a number of tyes of message between nodes ("Hi", "Here's a
job", "Success", etc), which means that each message sent needs to
carry a sentinel to identify what type it is, and, since debugging
issues from mismatched literals between the sending and receiving code
is vexing, I want to have a package with the shared constants that the
code for each side of the connection can use.
I started relatively simply (what passes for simple with me, anyway),
a DEFPACKAGE form with the exports, and a macro that created the
DEFCONSTANT forms.
This works, but has the ordinary issues related to the need to make
edits in two places.
It also has the extra problem that (since I'm working out protocol
issues as I go) sometimes one constant needs to become two, and sbcl
follows the spec and complains about package variance if the new
export list doesnt contain all of the symbols that were previously
exported.
Obviously what I'd like is a single macro form that would let me make
the edit in one place, and take care of the need to UNEXPORT any
symbols not in the current list, I'm just having issues figuring out
how to do so "correctly".
The problem is that the symbols for the DEFCONSTANT forms for the
PROGN must be interned in a package that won't exist until after the
PROGN is processed.
The situation is akin to the following code:
Code: Select all
(progn
(defpackage #:transcode-commands
(:use)
(:export "+HELLO+"))
(defconstant transcode-commands::+hello+ "HELLO"))
The way I opted to do it was to invoke MAKE-PACKAGE from thesbcl wrote: debugger invoked on a SB-INT:SIMPLE-READER-PACKAGE-ERROR in thread
#<THREAD "main thread" RUNNING {10028169A3}>:
Package TRANSCODE-COMMANDS does not exist.
macro-function if FIND-PACKAGE returned NIL, like so (slightly
simplified):
Code: Select all
(defmacro commands-package (name &body syms)
(let ((p (or (find-package name)
(make-package name))))
`(progn
(defpackage ,name
(:use)
(:export ,@syms))
,@ (mapcar (lambda (sym)
(let* ((name (symbol-name sym))
(shorn (subseq name
1
(1- (length name))))
(sym (intern name p)))
`(defconstant ,sym ,shorn)))
syms))))
not sure that it should work. As I pointed out in another thread some
time ago, side-effects in macro-functions are a no-no, because they
may be run many times, in an interpreter, or none at all if the
expansion was saved in a FASL. (I suspect in this case the sbcl FASL
format has some way to refer to symbols from non-existent packages).
I'm wondering whether I should abandon this approach, require an empty
DEFPACKAGE that is left alone, and then a separate macro that does the
business with EXPORT, or perhaps another approach entirely.
Thoughts, suggestions?