Let's say I have class A
with a couple of slots:
(defclass a ()
((a-1 :initarg :a-1)
(a-2 :initarg :a-2)))
And class B
that inherits from A
:
(defclass b (a)
((b-1 :initarg :b-1)))
If I want to instantiate B
, make-instance
will offer me slots :a-1
, :a-2
and :b-1
.
Here is a crazy idea: what if I want to instantiate B
using the existing instance of A
and only filling slot b-1
?
PS. Why it can be useful: if A
implements some generic methods that B
inherits directly, without adding anything new. In alternative approach, making instance of A
to be a slot in B
, I would need to write trivial method wrappers to invoke these methods on that slot.
The only way I can think of: in auxiliary constructor decompose object A
and pass corresponding slots to make-instance
for B
, i.e.:
(defun make-b (b-1 a-obj)
(with-slots (a-1 a-2) a-obj
(make-instance 'b :b-1 b-1 :a-1 a-1 :a-2 a-2)))
Are there better ways of doing this? (or perhaps, this approach leads to very bad design and I should avoid it altogether?)
I don't think, that there is a general solution. Consider: what should happen, for example, if class A
has some slots, which are not simply initialized from some :initarg
, but, say, during initialize-instance
or shared-initialize
?
That said, as long as you are in control of all involved classes, you could try
make a protocol implemented by A
, something along the lines of
(defgeneric initargs-for-copy (object)
(:method-combination append)
(:method append (object) nil))
(defmethod initargs-for-copy append ((object a))
(list :a-1 (slot-value object 'a-1) :a-2 (slot-value object 'a-2)))
(defun make-b (b-1 a-obj)
(apply #'make-instance 'b :b-1 b-1 (initargs-for-copy a-obj)))
use the MOP to extract the slots at run-time (this may require knowledge about the Lisp implementation of your choice, or the help of some library, like closer-mop
available via quicklisp)
(defun list-init-args (object)
(let* ((class (class-of object))
(slots (closer-mop:class-slots class)))
(loop
for slot in slots
for name = (closer-mop:slot-definition-name slot)
for keyword = (closer-mop:slot-definition-initargs slot)
when (and keyword (slot-boundp object name))
nconc (list (car keyword) (slot-value object name)))))
(defun make-b (b-1 a-obj)
(apply #'make-instance 'b :b-1 b-1 (list-init-args a-obj)))
use change-class
to transmogrify the A
instance into a B
instance
destructively.
Regardless: I am not sure, whether your use-case actually warrants inheritance. The composition approach seems (from a design point of view) much clearer here. Besides having B
inherit some generic method implementations via A
: are instances of B
really considered to be proper instances of A
in your actual application (i.e., is there an is-a?
relationship)? Or are you just trying to avoid having to provide the wrappers here?
What you are trying to do can be done using composition as a form of prototypal inheritance where an object "inherits" from another instance.
(defclass prototype-mixin ()
((parent :initarg :parent :initform nil :accessor parent)))
(defmethod slot-unbound (c (p prototype-mixin) slot)
(declare (ignore c))
(let ((parent (parent p)))
(if parent
(slot-value parent slot)
(call-next-method))))
Now, you define two classes:
(defclass a ()
((slot :initarg :slot)))
(defclass b (a prototype-mixin)
((other :initarg :other)))
When you create a b
from an existing instance of a
, you set the parent
slot of b
to a
. Since b
is also an a
, there is an unbound slot
in b
. When you try to access this slot, you access the one present in the "parent" object, which is an instance of a
. But if you want, you can override the value in b
.
This approach is inspired by a post from Erik Naggum on comp.lang.lisp.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With