This asks about initializing slots from other slots. What I want to achieve instead is to take some arguments as input - perhaps but not necessarily to make-instance - and convert these arguments into the class slots for storage. In effect, I want to separate the implementation of the class from its (initialization) interface.
Is there a recommended way to achieve this?
The simplest way I can imagine is simply create a (defun make-my-object ...) as the interface. This may then call make-instance with appropriate arguments.
For example, imagine
(defclass my-object () (slot-1 slot-2))
(defun make-my-object (arg-1 arg-2)
(make-instance 'my-object
:slot-1 (+ arg-1 arg-2)
:slot-2 (- arg-1 arg-2)))
Other ways I can imagine include implementing an initialize-instance :after that takes arg-1 and arg-2 as keyword arguments and initializes slot-1 and slot-2 appropriately. However, since after methods are called in the least-specific-first-order, that means that superclass slots will be initialized before current-class slot. On the other hand, what looks more common is that one will take arguments for constructing current-class, and on the basis of these arguments, one will initialize the super-class slots.
The alternative is initialize-instance :before - or also :around - but if multiple classes in the hierarchy have such "interface-implementation" differences, I don't see this working, unless I can pass arguments to call-next-method.
Are there other ways?
EDIT: Thanks to @ignis volens for bringing to my notice that (one of) my main concern(s) is about superclass slots being initialized from subclass slots. Is there a recommended way to do this?
I am not sure I understand your question. The answer is almost certainly after methods on
initialize-instanceI think. You say that this will cause slots defined in superclasses to be initialized first: yes, it will, and that almost certainly what you want to happen. Slots defined in superclasses don't generally depend for their values on subclass slots (its always possible to think of exceptions to everything) and so initialising in least-specific first order is almost always what you want.The two common ways of initializing slots that I use are either to simply declare what their initargs are in the definition:
And now
(make-instance 'minibeast :legs 87)does what you expect. And this works (because, obviously it has to if the two slots were defined in different classes):Now
(make-instance 'awful-monster :legs 93)will result in an awful monster with 93 legs and 93 appendages.However that method perhaps doesn't qualify as separating interface from implementation. You may also want to perform some computation when initialising slots. In both these cases after methods on
initialize-instanceare generally the right approach:And now horrible monsters will get the appropriate number of appendages (I am not sure why horrible monsters don't know whether their eyes are on stalks or not: perhaps they don't have mirrors).
There are any number of other combinations of course. You might well not want to have user code call
make-instanceexplicitly but wrap things up in some function:And now, of course you can have some bespoke initialzation protocol easily: