Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pass a super-class object to a sub-class constructor?

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?)

like image 959
mobiuseng Avatar asked Mar 14 '23 10:03

mobiuseng


2 Answers

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?

like image 131
Dirk Avatar answered Apr 28 '23 10:04

Dirk


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.

like image 34
coredump Avatar answered Apr 28 '23 11:04

coredump