On Tue, 4 Aug 2015 03:55:30 -0400, Jean-Claude Beaudoin said:
But I find sad that we'd have to force defstruct to be a top-level only form with this workaround instead of letting perfectly normal language constructs intermix freely as the problem would find appropriate and natural.
My interest here is that I am reworking the implementation of the DEFSTRUCT macro in MKCL. And honestly I am of the opinion that there is a pretty strong case to be made in favor of the clisp behavior on this issue. But I cannot figure out the justification for the other behavior illustrated by almost all of the other implementations. Is this just some long standing historical quirk at play here or is there really a reason for this way of doing it?
Probably because remembering the initform and splicing it into the constructor of the included defstruct is the most obvious implementation.
It is easy to make that implementation evaluate the initform in the correct lexical environment by including the constructor in the macroexpansion of defstruct. Doing that for included defstructs requires more effort to avoid slowdown in the simple cases.
Another related case is EQ'ness of the initform values. For example, with two files:
q1.lisp: (defstruct q1 (a '(1 2 3))) (defun t1 () (eq (q1-a (make-q1)) (q1-a (make-q1))))
q2.lisp: (defstruct (q2 (:include q1))) (defun t12 () (eq (q1-a (make-q1)) (q1-a (make-q2))))
If you compile and load each of these files, then t1 should always return t whereas t12 might return nil or t depending on whether the code for the initform in q1 is shared or copied.