Am 07.08.2012 um 14:58 schrieb Erik Huelsmann ehuels@gmail.com:
Thanks for the report. I've been thinking about it and I think the best solution is even easier to code: when an included DEFSTRUCT already defines the same accessor, we should simply not define it again: the result will be that the accessor checks for structs of type 'A and all of its subtypes will be allowed.
I thought about this, but the problem with my example (repeated here for convenience)
(defstruct a (s1 nil))
(defstruct (b (:include a) (:conc-name foo-)) (s2 nil))
(defstruct (c (:include a) (:conc-name foo-)) (s3 nil))
is that, if we focus on defstruct "c", it is not the included defstruct "a" which defines foo-s1 but it is defstruct "b", which is not related to "c" at all. Thus, in a naive view, the code that generates "c" has to have a look at all substructs of those which are (directly or indirectly) included into "c" (possibly going up and down in the hierarchy several times). I can hardly imagine that this idea will lead to a stable implementation. So, the code for "b" must somehow store in the management info for defstruct "a" that there is a successor function foo-s1 for a's slot s1 (and the accessor is called foo-s1).
Ah. I thought the problem was in the foo-s1 accessor being generated multiple times. Sorry I missed the important bit where the definition of A wasn't also defining the :nconc-name FOO-.
This slot accessor is not to be generated again for "c", of course, as you said. The strange thing this is that, in principle, foo-s1 is not applicable to instances of "a", but to instances of "b" and "c" only. Since new sub-structures of "a" (with conc-name foo-) can be defined at any time, the type for the applicable structures used in the implementation of foo-s1 cannot be statically encoded but must be dynamically determined, or the code for foo-s1 could be dynamically adapted for every new sub-defstruct of "a" with conc-name foo-. I tend to believe this is not easy to implement.
Actually, it's really easy to implement.
I am afraid, using defmethod for the non-optimized case is not that easy because not all defstructs are necessarily compiled with the same optimization settings (e.g., they might be located in different files). In addition, one has to remove methods if a defstruct is recompiled with other slots or with different optimization settings..... :-(
But the runtime cost of doing so is huge, unfortunately: we can simply use DEFMETHOD to define the accessors. That will automatically notice the accessor is already defined and add a method for each additionally defined type.
But, the performance of a method is by no means as efficient as the current method.
You're talking about adding types to be checked for dynamically to the current accessors. That's indeed far from trivial: the defstruct generates inline expansions. These expansions would have to be "dynamically changeable".
As far as your change to remove the type checks goes: The type checks were added by me a few years ago exactly because the 'type-check-less' versions didn't catch errors in my code. I mean to say: the original ones were without type checks.
I see multiple options:
- Remove the type checks in the inline expansions and use DEFMETHOD for the accessor functions, where we need to use the methods only with sufficiently high SAFETY declarations
- Use your patch to remove the type checks completely
- Come up with a way to add dynamic type checks to the inline expansions and function definitions (ie not methods) - possibly executed conditionally with safety settings.
- Do nothing (not saying it's a good option, but it's always an option)
Do you see any others? I'm not sure about the performance impact of dynamic checks, but I do think that if we decide to apply a caching strategy, using the slow-path of updating the cache in case of a type-mismatch, the impact may not be much worse than it is today. After all, the biggest change would be to generate code which allows to check more than one type instead of the current single type.
Bye,
Erik.