1

I am reading the book Object-Oriented Programming in Common Lisp by Sonja Keene.

In Chapter 9, the author presents the following example:

(defclass window ()
    ((x :initarg :x-position :accessor x-position)
     (y :initarg :y-position :accessor y-position)
     (height :initarg :height :accessor window-height)
     (width :initarg :width :accessor window-width)
     (exposed-p :initform nil :accessor exposed-p))
   (:documentation "Foundation of all windows"))

I know the use of :, :: or its absence goes beyond pure notation in Common Lisp when talking about packages. There is a logic behind its use!

However, I cannot see a logic behind the use of : on slot options/specifiers. For instance, consider the example above.

Look at the first slot, the x. There is the slot option :initarg associated with :x-position. OK, both include :.

Now, on the same slot, look to the :accessor slot option. It includes the double dot :. However, the x-position associated with it does not include the :.

Is there some logic behind this syntax?

The best hypothesis I have is that the syntax design is used to reflect the difference of moments when dealing with the instances. Thus, the absence of the double-dot would indicate an use related to pos-initialization and the inclusion of the double would be related to the on-going initialization process:

CL-USER> (x-position  (make-instance 'window :x-position 3))
3

Is there something else? Did I miss something? Is it pure syntax convention and what I said is just a coincidence?

Thanks

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Pedro Delfino
  • 2,421
  • 1
  • 15
  • 30

3 Answers3

6

Remember, a single colon means that the symbol :bar

  • is a keyword
  • and is in the keyword package
  • and is exported from the keyword package
  • and has the constant value of itself (-> :bar).

Try

(defclass foo ()
  ((x
    :initarg   x-position
    :accessor  x-position)))

* (make-instance 'foo 'x-position 3)
#<FOO {7007741DD3}>

* (x-position *)
3

and

(defclass foo ()
  ((x
    :initarg   x-position
    :accessor :x-position)))

* (make-instance 'foo 'x-position 4)
#<FOO {700778D473}>
* (:x-position *)
4

The initialization argument is a keyword argument to the function make-instance (and related). The accessor is the name of a function and the name of a setf accessor.

One can make the initarg any symbol, instead of a keyword symbol. It's just usual a keyword, since generally function keywords are given with keyword symbols.

One can also try to make the accessor name a symbol and not specifically a keyword symbol. An implementation might or might not support that. It would mean that the implementation needs to support keywords as function names.

Style and Convention

Usually we write the initarg option as a keyword symbol and the accessor option as a non-keyword symbol.

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

The :accessor option is used to define a method name. Function names should always be in the application's package, so that you don't have conflicts between two packages that define a slot with the same name.

If you used a keyword :x-position, and some other package did the same thing, they would conflict with each other.

Barmar
  • 741,623
  • 53
  • 500
  • 612
1

As Barmar explained, defining an accessor function with a keyword symbol would be a source of conflict. Accessor functions are generic functions, so it might seem that reusing the same symbol for different cases might still work. For example, two different unrelated classes c1 or c2 defining :x-position as an accessor could be used as follows, for any object o of class either c1 or c2:

(:x-position o)

The dynamic dispatch of the unique generic function :x-position would work. But since there is only one generic function, the other methods or properties of that generic functions are also shared, even if it is not desired. This includes for example the method combination mechanism of the generic function, :around methods, etc. Also, if any library defines a method for a standard type (like :x-position of a number), the behavior is shared with the other library. That's why the accessor function is always defined as a normal package symbol.

On the other hand, the same problem does not arise with the :initarg argument. Having two unrelated class with the same :x-position initarg for one of their respective slots is not a problem since there is a different context where each keyword is used.

When calling make-instance, the code dispatches to the specific initialization code that makes use of the different initargs. The only case where a keyword initarg could be a problem is if a class inherits from two super classes that both rely on the same keyword, as in the following example:

USER> (defclass foo () ((x :initarg :x)))                                                                                                                                                     
#<STANDARD-CLASS FOO>                                                                                                                                                            

USER> (defclass bar () ((z :initarg :x)))                                                                                                                                                     
#<STANDARD-CLASS BAR>                                                                                                                                                            

USER> (defclass zot (foo bar) ())                                                                                                                                                             
#<STANDARD-CLASS ZOT>                                                                                                                                                            

USER> (make-instance 'zot :x 10)                                                                                                                                                              
#<ZOT {10252705D3}>                                                                                                                                                                           

USER> (describe *)                                                                                                                                                                            
#<ZOT {10252705D3}>                                                                                                                                                                           
  [standard-object]                                                                                                                                                                           
                                                                                                                                                                                              
Slots with :INSTANCE allocation:                                                                                                                                                              
  Z                              = 10                                                                                                                                                         
  X                              = 10                                                                                                                                                         
; No values                                                                                                                                                                                   

Here above, the same :x initarg is used to initialize two slots. Fortunately, if this is a problem that would be a rare one, and it can be alleviated by adding more initargs to the slots:

USER> (defclass zot () ((x :initarg :foo-x) (z :initarg :bar-x)))                                                                                                                             
#<STANDARD-CLASS ZOT> 
                                                                                                                                                           
USER> (describe (make-instance 'zot :foo-x 5 :bar-x 10))                                                                                                                                      
#<ZOT {10253A5373}>                                                                                                                                                                           
  [standard-object]                                                                                                                                                                           
                                                                                                                                                                                              
Slots with :INSTANCE allocation:                                                                                                                                                              
  X                              = 5                                                                                                                                                          
  Z                              = 10     
coredump
  • 37,664
  • 5
  • 43
  • 77