3

In Python, I would do like this:

class foo:
    def __init__(self):
        self.x = self

Otherwise, now the object is a parameter of itself. How can I do it in common lisp?

(defclass mn ()
  ((pai   :accessor mn-pai
          :initarg :pai
          :initform self)))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346

3 Answers3

8

In a DEFCLASS slot description one can't reference the object itself. But one can write methods for instance initialization. This would be similar to your Python example.

Our class:

? (defclass foo ()
    ((bar :accessor foo-bar :initarg :foo)))
#<STANDARD-CLASS FOO>

We create an :after method for initialize-instance. This generic function is provided by CLOS and its purpose is to initialize a new instance. The first argument is the instance to initialize. The method will be called by the Lisp system when we create an instance of the class foo.

Using the accessor foo-bar:

? (defmethod initialize-instance :after ((object foo) &key)
    (setf (foo-bar object) object))
#<STANDARD-METHOD INITIALIZE-INSTANCE :AFTER (FOO)>

or setting the slot via (setf slot-value).

? (defmethod initialize-instance :after ((object foo) &key)
    (setf (slot-value object 'bar) object))
#<STANDARD-METHOD INITIALIZE-INSTANCE :AFTER (FOO)>

Note that we can name the instance parameter with any name: object or even self. But the name has no semantics. Since in CLOS we have multi-dispatch (dispatch can work about more than one argument and there is no default dispatched argument), there is no self semantics.

Now we make and then describe an instance of class foo:

? (describe (make-instance 'foo))
#<FOO #x302000D20C0D>
Class: #<STANDARD-CLASS FOO>
Wrapper: #<CCL::CLASS-WRAPPER FOO #x302000D2B43D>
Instance slots
BAR: #<FOO #x302000D20C0D>

As you can see, the slot bar of that instance has been set to the instance itself.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
5

Note that the initform is evaluated in the lexical context of defclass, but the dynamic context of make-instance. This allows you to define a special variable named *this* (you could use this, but that could be confusing) and use it when you initialize objects.

(defvar *this*)

Define a mixin for classes that may reference *this*:

(defclass knows-this () ())

(defmethod shared-initialize :around ((object knows-this) slot-names &rest args)
  (declare (ignore args))
  (let ((*this* object))
    (call-next-method)))

For example:

(defclass foo (knows-this)
  ((myself :initform *this*)))

(describe (make-instance 'foo))

#<FOO {100AC6EF13}>
  [standard-object]

Slots with :INSTANCE allocation:
  MYSELF                         = #<FOO {100AC6EF13}>
coredump
  • 37,664
  • 5
  • 43
  • 77
0

CLOS doesn't have the concept of "this" or "self" because through the use of generic functions, whatever instance is being acted upon is passed as an argument.

So, given your example with the accessor mn-pai:

(setf instance (make-instance 'mn))
(mn-pai instance 1)

Here, instance is passed as an argument to the accessor.

If you created a method:

(defmethod inc-pai (an-mn amount)
  (incf (mn-pai an-mn) amount))

Again, you see the instance is passed in as the first argument. So, there's always an explicit argument that you use.

Now consider:

(defmethod inc-both (an-mn another-mn amount)
  (incf (mn-pai an-mn) amount)
  (incf (mn-pai another-mn) amount))

So, in a normal class based system, where would you put this method? In a Utility class? Is this a "mn" class method? It sort of defies ready categorization.

Now consider:

(defclass mn2 ()
  ((pai   :accessor mn2-pai)))

if we were to do this:

(setf an-mn (make-instance 'mn))
(setf an-mn2 (make-instance 'mn2))
(inc-both an-mn an-mn2)

The second line would fail, as mn2 does not have a mn-pai accessor.

However, this would work:

(defmethod inc-both2 (an-mn another-mn amount)
    (incf (slot-value 'pai an-mn) amount)
    (incf (slot-value 'pai another-mn) amount))

Because the slot-value is the primitive accessor for CLOS, and both classes have a slot named pai. But, then you don't get to call the accessor function. Rather you're setting the slot directly. Probably not what you want. And, of course, the names are coincidence. There's no relationship between the classes save their similar names and a shared slot name.

But you can then do this:

(defmethod inc-both ((mn an-mn) (mn2 another-mn) amount)
  (incf (mn-pai an-mn) amount)
  (incf (mn-pai2 another-mn) amount))

This works because the runtime will dispatch based on the types of the parameters. We "know" another-mn is an instance of mn2 because we told the system that it must be when we qualified the argument.

But, again, you can see how in a class based system, there's no "place" for this kind of method. We typically just create a Utility class of some kind and stick these in there, or a regular function in the global namespace.

While CLOS has classes, it's not really a class based system.

This also comes up in multi-inheritance scenarios (which CLOS supports). Who is "self" then?

Will Hartung
  • 115,893
  • 19
  • 128
  • 203
  • 1
    runtime dispatch in CLOS is not based on types, it is based on classes. The dispatch arguments are in the wrong order. It also does not 'know' the class of an object because one told it, but because the object carries its class around. For `inc-both` it makes no difference to dispatch on the classes or not. – Rainer Joswig Jun 13 '18 at 03:28