On 18 Aug 2007, at 20:49, Nikodemus Siivola wrote:
> On 8/18/07, Pascal Costanza <pc@...> wrote:
> I think there mainly two ways to approach sealing:
> a) From the point of view of "what promises do I need to make to
> get efficient code"?
> Looking at things this way, you want to commit to as little
> restrictions as possible (to retain development flexibilty),
> as long as they are sufficient to allow the desired efficiencies.
> b) From the point of view of "I'm doing something tricky here,
> and additional restrictions make it easier".
> In this case you want to be as restrictive as possible as long
> as those restrictions serve your needs.
> I there some sort of consensus on these points?
Yes, I think these are basically the two options.
> I'm currently looking at sealing from the first point, though I have
> no quarrel with the second as a desire.
It may even make sense to use the first point as the low-level
starting point on which semantically interesting properties can be
> From that POV, I currently find the following things most promising
> * Sealing direct slot locations, types, and boundpness selectively.
> Sealing direct slots because when you seal effective slots
> you restrict features of the superclass: it cannot suddenly
> lose those slots anymore.
> Sealing selectively, so that when you are working on the system
> you can add slots to an already sealed class, and remove them
> again as long as you don't seal them in between.
> Sealing slot features, as they help making various slot
> accessors fast. (Though I've discovered that the permutation
> vector system PCL uses for slot-accesses inside method bodies
> is hard to beat with a decent margin!)
I have a hard time imagining that sealing slot locations or sealing
types really buy that much. Types may be interesting for machine
types (like fixnum), but apart from that, I don't think they are
interesting from an efficiency perspective.
What's more interesting on modern CPUs, I guess, is that caches are
rarely invalidated and that variables used in combination are close
to each other in memory. That's the case anyway for objects.
Efficiency because of fixed slot locations sounds really 80's to me. ;)
Declaring that a slot is never unbound seems to me to be more likely
to buy something, but I am not sure here.
> I cannot stress the value of being able to still modify the class
> hierarchy in various way too much: otherwise you cannot seal things
> at all for normal development, and your performance measurements
> will not be correct.
I am surprised to hear this. Is efficiency really that important at
development time? Maybe I am too old-fashioned, but I am thinking
about optimization as being typically one of the last steps during
>> (1) I may want to say that a particular class should not be redefined
>> (2) I may want to say that a particular class and all its current
>> should not be redefined anymore. [Can easily be expressed if we
>> have (1).]
>> (3) I may want to say that the class hierarchy from a certain class
>> downwards is closed - i.e., it should not be allowed to add more
>> to that subtree.
> All sound reasonable to me, but I'm not seeing huge optimization
> from them. Being able to infer that X holds true for all subclasses of
> C is quite valuable. Knowing the full set of subclasses, or _all_
> aspects of C seems less useful.
This may be useful for optimizing generic function dispatch, though.
For example, it may become feasible to perform call-site dispatch
without the need to recompile code because of potential changes in
> Contrariwise, these are not sufficient guarantees to make faster
> slot accesses
> possible: ideally
> (defmethod foo ((bar bar)) (setf (slot-value bar 'x) (slot-value
> bar 'y)))
> can compile into something like
> (setf (svref #:slots 0) (svref #:slots 1))
> but this is possible only with stronger guarantees -- but doesn't
> locking _everything_ down -- just having a way to ensure that all
> of BAR have X at location 0, and Y at location 1, and being able to
> know this
> when the method is compiled.
> Am I missing something here?
If you close a hierarchy from a certain class downwards _and_ ensure
that the respective classes cannot be redefined anymore, you can
define and fix "optimal" slot locations for all involved classes.
This is basically what defstruct gives you (without closing
hierarchies, but determining "optimal" slot locations for single
inheritance is much easier anyway).
>> While we're at it:
>> (4) I may want to say that a particular generic function should
>> not be
>> redefined anymore (methods should not be added or removed).
>> (5) I may want to say that the set of specializers covered by a
>> generic function is complete (methods may be redefined, but
>> methods for new
>> specializers shouldn't be acceptable anymore).
>> I think these are the kinds of things one may want to say because
>> they are
>> semantically meaningful, and at the same time, they should allow
>> implementors to create more efficient code.
> I'm there with you on 4 and 5, but as said I'm not sure 1-3 give
> the kind of
> guarantees some optimizations require, and I also find them overly
> when considering working with code that uses sealing.
I hope 1-3 is clearer. I understand your argument, but as I said, I
find the idea weird of sealing some slot locations while the overall
class definition isn't fixed yet.
>> Dylan seems to have something like (3) and (4), and maybe (5) (but
>> I don't
>> completely understand the Dylan specification in that regard).
> Actually, Dylan has one _very_ interesting feature: primary
> classes. You
> are only allowed to inherit from a single primary class --
> basically the
> same restriction that I need to impose when sealing slot locations.
> Doing things what way would certainly lead to a simpler interface, but
> the behavior wrt. redefinitions (which I want, as long as they are
> congruent) might get tricky.
I recall reading a paper about a suggestion for EuLisp to distinguish
between classes and mixins. In CLOS, mixins is "just" a programming
style enabled by multiple inheritance, whereas if you would make the
distinction between classes and mixins explicit, you could get some
benefits (including simpler linearization).
I guess that almost all uses of multiple inheritance in CLOS are
actually uses of mixins. (But of course, it's dangerous to make such
statements, because nobody knows all CLOS programs...)
>> What's important, I think, is that the interface for sealing
>> should be
>> simple enough that the effects are easily predictable and should
>> potential hard-to-find bugs. I am not convinced that Nikodemus's
>> is simple enough in that regard.
> I sort of hoping this is due to my awful prose...
> I've appended the cleaned up version of the SEAL-DIRECT-SLOTS
> docstring at the
> end of this email -- it's even longer but the language is hopefully
Thanks for reformulating. I don't understand the second and the fifth
error condition. Aren't they too restrictive? Shouldn't the signal
only an error if the set of sealed slots for different classes in the
same precedence list overlap _and_ have different locations?
Also, the final note for COMPUTE-CLASS-PRECEDENCE-LIST is redundant.
C-C-P-L must return a list that contains the class and all direct and
indirect superclasses in question, and all exactly once, anyway. (But
maybe it's a good idea to keep this.)
>> With the current CLOS MOP, sparse instances should be possible:
>> Just add
>> more slots in compute-slots with gensymmed names. Or am I missing
> Yes and no. Certainly doable that way, but not really sparse, in
> the sense
> that those gensym-named slots are there, and if you ask the class
> for it's
> slots you can get at those gensyms and therefore at the slots.
You could define a new :allocation (like :hidden) that implies that
an error is signalled when such a slot is accessed. (Hm, maybe that's
too gross - just brainstorming...)
Pascal Costanza, mailto:pc@..., http://p-cos.net
Vrije Universiteit Brussel, Programming Technology Lab
Pleinlaan 2, B-1050 Brussel, Belgium